home *** CD-ROM | disk | FTP | other *** search
- #include <assert.h>
- #include "cvs.h"
- #include "watch.h"
- #include "edit.h"
- #include "fileattr.h"
-
- #ifdef SERVER_SUPPORT
-
- /* for select */
- #include <sys/types.h>
- #ifdef HAVE_SYS_BSDTYPES_H
- #include <sys/bsdtypes.h>
- #endif
- #include <sys/time.h>
-
- #if HAVE_SYS_SELECT_H
- #include <sys/select.h>
- #endif
-
- #if HAVE_FCNTL_H
- #include <fcntl.h>
- #endif
-
- #ifndef O_NONBLOCK
- #define O_NONBLOCK O_NDELAY
- #endif
-
- #ifdef AUTH_SERVER_SUPPORT
- /* For initgroups(). */
- #if HAVE_INITGROUPS
- #include <grp.h>
- #endif /* HAVE_INITGROUPS */
- #endif /* AUTH_SERVER_SUPPORT */
-
-
- /* Functions which the server calls. */
- int add PROTO((int argc, char **argv));
- int admin PROTO((int argc, char **argv));
- int checkout PROTO((int argc, char **argv));
- int commit PROTO((int argc, char **argv));
- int diff PROTO((int argc, char **argv));
- int history PROTO((int argc, char **argv));
- int import PROTO((int argc, char **argv));
- int cvslog PROTO((int argc, char **argv));
- int patch PROTO((int argc, char **argv));
- int release PROTO((int argc, char **argv));
- int cvsremove PROTO((int argc, char **argv));
- int rtag PROTO((int argc, char **argv));
- int status PROTO((int argc, char **argv));
- int tag PROTO((int argc, char **argv));
- int update PROTO((int argc, char **argv));
-
-
- /*
- * This is where we stash stuff we are going to use. Format string
- * which expects a single directory within it, starting with a slash.
- */
- static char *server_temp_dir;
-
- /* Nonzero if we should keep the temp directory around after we exit. */
- static int dont_delete_temp;
-
- static char no_mem_error;
- #define NO_MEM_ERROR (&no_mem_error)
-
- static void server_write_entries PROTO((void));
-
- /*
- * Read a line from the stream "instream" without command line editing.
- *
- * Action is compatible with "readline", e.g. space for the result is
- * malloc'd and should be freed by the caller.
- *
- * A NULL return means end of file. A return of NO_MEM_ERROR means
- * that we are out of memory.
- */
- static char *read_line PROTO((FILE *));
-
- static char *
- read_line (stream)
- FILE *stream;
- {
- int c;
- char *result;
- int input_index = 0;
- int result_size = 80;
-
- fflush (stdout);
- result = (char *) malloc (result_size);
- if (result == NULL)
- return NO_MEM_ERROR;
-
- while (1)
- {
- c = fgetc (stream);
-
- if (c == EOF)
- {
- free (result);
- return NULL;
- }
-
- if (c == '\n')
- break;
-
- result[input_index++] = c;
- while (input_index >= result_size)
- {
- result_size *= 2;
- result = (char *) realloc (result, result_size);
- if (result == NULL)
- return NO_MEM_ERROR;
- }
- }
-
- result[input_index++] = '\0';
- return result;
- }
-
- /*
- * Make directory DIR, including all intermediate directories if necessary.
- * Returns 0 for success or errno code.
- */
- static int mkdir_p PROTO((char *));
-
- static int
- mkdir_p (dir)
- char *dir;
- {
- char *p;
- char *q = malloc (strlen (dir) + 1);
- int retval;
-
- if (q == NULL)
- return ENOMEM;
-
- /*
- * Skip over leading slash if present. We won't bother to try to
- * make '/'.
- */
- p = dir + 1;
- while (1)
- {
- while (*p != '/' && *p != '\0')
- ++p;
- if (*p == '/')
- {
- strncpy (q, dir, p - dir);
- q[p - dir] = '\0';
- if (CVS_MKDIR (q, 0777) < 0)
- {
- if (errno != EEXIST
- && (errno != EACCES || !isdir(q)))
- {
- retval = errno;
- goto done;
- }
- }
- ++p;
- }
- else
- {
- if (CVS_MKDIR (dir, 0777) < 0)
- retval = errno;
- else
- retval = 0;
- goto done;
- }
- }
- done:
- free (q);
- return retval;
- }
-
- /*
- * Print the error response for error code STATUS. The caller is
- * reponsible for making sure we get back to the command loop without
- * any further output occuring.
- */
- static void
- print_error (status)
- int status;
- {
- char *msg;
- printf ("error ");
- msg = strerror (status);
- if (msg)
- printf ("%s", msg);
- printf ("\n");
- }
-
- static int pending_error;
- /*
- * Malloc'd text for pending error. Each line must start with "E ". The
- * last line should not end with a newline.
- */
- static char *pending_error_text;
-
- /* If an error is pending, print it and return 1. If not, return 0. */
- static int
- print_pending_error ()
- {
- if (pending_error_text)
- {
- printf ("%s\n", pending_error_text);
- if (pending_error)
- print_error (pending_error);
- else
- printf ("error \n");
- pending_error = 0;
- free (pending_error_text);
- pending_error_text = NULL;
- return 1;
- }
- else if (pending_error)
- {
- print_error (pending_error);
- pending_error = 0;
- return 1;
- }
- else
- return 0;
- }
-
- /* Is an error pending? */
- #define error_pending() (pending_error || pending_error_text)
-
- int
- supported_response (name)
- char *name;
- {
- struct response *rs;
-
- for (rs = responses; rs->name != NULL; ++rs)
- if (strcmp (rs->name, name) == 0)
- return rs->status == rs_supported;
- error (1, 0, "internal error: testing support for unknown response?");
- /* NOTREACHED */
- return 0;
- }
-
- static void
- serve_valid_responses (arg)
- char *arg;
- {
- char *p = arg;
- char *q;
- struct response *rs;
- do
- {
- q = strchr (p, ' ');
- if (q != NULL)
- *q++ = '\0';
- for (rs = responses; rs->name != NULL; ++rs)
- {
- if (strcmp (rs->name, p) == 0)
- break;
- }
- if (rs->name == NULL)
- /*
- * It is a response we have never heard of (and thus never
- * will want to use). So don't worry about it.
- */
- ;
- else
- rs->status = rs_supported;
- p = q;
- } while (q != NULL);
- for (rs = responses; rs->name != NULL; ++rs)
- {
- if (rs->status == rs_essential)
- {
- printf ("E response `%s' not supported by client\nerror \n",
- rs->name);
- exit (EXIT_FAILURE);
- }
- else if (rs->status == rs_optional)
- rs->status = rs_not_supported;
- }
- }
-
- static int use_dir_and_repos = 0;
-
- static void
- serve_root (arg)
- char *arg;
- {
- char *env;
- extern char *CVSroot;
- char path[PATH_MAX];
- int save_errno;
-
- if (error_pending()) return;
-
- (void) sprintf (path, "%s/%s", arg, CVSROOTADM);
- if (!isaccessible (path, R_OK | X_OK))
- {
- save_errno = errno;
- pending_error_text = malloc (80 + strlen (path));
- if (pending_error_text != NULL)
- sprintf (pending_error_text, "E Cannot access %s", path);
- pending_error = save_errno;
- }
- (void) strcat (path, "/");
- (void) strcat (path, CVSROOTADM_HISTORY);
- if (isfile (path) && !isaccessible (path, R_OK | W_OK))
- {
- save_errno = errno;
- pending_error_text = malloc (80 + strlen (path));
- if (pending_error_text != NULL)
- sprintf (pending_error_text, "E \
- Sorry, you don't have read/write access to the history file %s", path);
- pending_error = save_errno;
- }
-
- CVSroot = malloc (strlen (arg) + 1);
- if (CVSroot == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (CVSroot, arg);
- #ifdef HAVE_PUTENV
- env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
- if (env == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- (void) sprintf (env, "%s=%s", CVSROOT_ENV, arg);
- (void) putenv (env);
- /* do not free env, as putenv has control of it */
- #endif
- }
-
- /*
- * Add as many directories to the temp directory as the client tells us it
- * will use "..", so we never try to access something outside the temp
- * directory via "..".
- */
- static void
- serve_max_dotdot (arg)
- char *arg;
- {
- int lim = atoi (arg);
- int i;
- char *p;
-
- if (lim < 0)
- return;
- p = malloc (strlen (server_temp_dir) + 2 * lim + 10);
- if (p == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (p, server_temp_dir);
- for (i = 0; i < lim; ++i)
- strcat (p, "/d");
- free (server_temp_dir);
- server_temp_dir = p;
- }
-
- static char *dir_name;
-
- static void
- dirswitch (dir, repos)
- char *dir;
- char *repos;
- {
- int status;
- FILE *f;
-
- server_write_entries ();
-
- if (error_pending()) return;
-
- if (dir_name != NULL)
- free (dir_name);
-
- dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 40);
- if (dir_name == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
-
- strcpy (dir_name, server_temp_dir);
- strcat (dir_name, "/");
- strcat (dir_name, dir);
-
- status = mkdir_p (dir_name);
- if (status != 0
- && status != EEXIST)
- {
- pending_error = status;
- pending_error_text = malloc (80 + strlen(dir_name));
- sprintf(pending_error_text, "E cannot mkdir %s", dir_name);
- return;
- }
- if (chdir (dir_name) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(dir_name));
- sprintf(pending_error_text, "E cannot change to %s", dir_name);
- return;
- }
- /*
- * This is pretty much like calling Create_Admin, but Create_Admin doesn't
- * report errors in the right way for us.
- */
- if (CVS_MKDIR (CVSADM, 0777) < 0)
- {
- if (errno == EEXIST)
- /* Don't create the files again. */
- return;
- pending_error = errno;
- return;
- }
- f = fopen (CVSADM_REP, "w");
- if (f == NULL)
- {
- pending_error = errno;
- return;
- }
- if (fprintf (f, "%s\n", repos) < 0)
- {
- pending_error = errno;
- fclose (f);
- return;
- }
- if (fclose (f) == EOF)
- {
- pending_error = errno;
- return;
- }
- f = fopen (CVSADM_ENT, "w+");
- if (f == NULL)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT);
- return;
- }
- if (fclose (f) == EOF)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
- return;
- }
- }
-
- static void
- serve_repository (arg)
- char *arg;
- {
- dirswitch (arg + 1, arg);
- }
-
- static void
- serve_directory (arg)
- char *arg;
- {
- char *repos;
- use_dir_and_repos = 1;
- repos = read_line (stdin);
- if (repos == NULL)
- {
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
- {
- if (feof (stdin))
- sprintf (pending_error_text,
- "E end of file reading mode for %s", arg);
- else
- {
- sprintf (pending_error_text,
- "E error reading mode for %s", arg);
- pending_error = errno;
- }
- }
- else
- pending_error = ENOMEM;
- }
- else if (repos == NO_MEM_ERROR)
- {
- pending_error = ENOMEM;
- }
- else
- {
- dirswitch (arg, repos);
- free (repos);
- }
- }
-
- static void
- serve_static_directory (arg)
- char *arg;
- {
- FILE *f;
- f = fopen (CVSADM_ENTSTAT, "w+");
- if (f == NULL)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
- return;
- }
- if (fclose (f) == EOF)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
- return;
- }
- }
-
- static void
- serve_sticky (arg)
- char *arg;
- {
- FILE *f;
- f = fopen (CVSADM_TAG, "w+");
- if (f == NULL)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_TAG));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_TAG);
- return;
- }
- if (fprintf (f, "%s\n", arg) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_TAG));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_TAG);
- return;
- }
- if (fclose (f) == EOF)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_TAG));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_TAG);
- return;
- }
- }
-
- /*
- * Read SIZE bytes from stdin, write them to FILE.
- *
- * Currently this isn't really used for receiving parts of a file --
- * the file is still sent over in one chunk. But if/when we get
- * spiffy in-process gzip support working, perhaps the compressed
- * pieces could be sent over as they're ready, if the network is fast
- * enough. Or something.
- */
- static void
- receive_partial_file (size, file)
- int size;
- int file;
- {
- char buf[16*1024], *bufp;
- int toread, nread, nwrote;
- while (size > 0)
- {
- toread = sizeof (buf);
- if (toread > size)
- toread = size;
-
- nread = fread (buf, 1, toread, stdin);
- if (nread <= 0)
- {
- if (feof (stdin))
- {
- pending_error_text = malloc (80);
- if (pending_error_text)
- {
- sprintf (pending_error_text,
- "E premature end of file from client");
- pending_error = 0;
- }
- else
- pending_error = ENOMEM;
- }
- else if (ferror (stdin))
- {
- pending_error_text = malloc (40);
- if (pending_error_text)
- sprintf (pending_error_text,
- "E error reading from client");
- pending_error = errno;
- }
- else
- {
- pending_error_text = malloc (40);
- if (pending_error_text)
- sprintf (pending_error_text,
- "E short read from client");
- pending_error = 0;
- }
- return;
- }
- size -= nread;
- bufp = buf;
- while (nread)
- {
- nwrote = write (file, bufp, nread);
- if (nwrote < 0)
- {
- pending_error_text = malloc (40);
- if (pending_error_text)
- sprintf (pending_error_text, "E unable to write");
- pending_error = errno;
- return;
- }
- nread -= nwrote;
- bufp += nwrote;
- }
- }
- }
-
- /* Receive SIZE bytes, write to filename FILE. */
- static void
- receive_file (size, file, gzipped)
- int size;
- char *file;
- int gzipped;
- {
- int fd;
- char *arg = file;
- pid_t gzip_pid = 0;
- int gzip_status;
-
- /* Write the file. */
- fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
- if (fd < 0)
- {
- pending_error_text = malloc (40 + strlen (arg));
- if (pending_error_text)
- sprintf (pending_error_text, "E cannot open %s", arg);
- pending_error = errno;
- return;
- }
-
- /*
- * FIXME: This doesn't do anything reasonable with gunzip's stderr, which
- * means that if gunzip writes to stderr, it will cause all manner of
- * protocol violations.
- */
- if (gzipped)
- fd = filter_through_gunzip (fd, 0, &gzip_pid);
-
- receive_partial_file (size, fd);
-
- if (pending_error_text)
- {
- char *p = realloc (pending_error_text,
- strlen (pending_error_text) + strlen (arg) + 30);
- if (p)
- {
- pending_error_text = p;
- sprintf (p + strlen (p), ", file %s", arg);
- }
- /* else original string is supposed to be unchanged */
- }
-
- if (close (fd) < 0 && !error_pending ())
- {
- pending_error_text = malloc (40 + strlen (arg));
- if (pending_error_text)
- sprintf (pending_error_text, "E cannot close %s", arg);
- pending_error = errno;
- if (gzip_pid)
- waitpid (gzip_pid, (int *) 0, 0);
- return;
- }
-
- if (gzip_pid)
- {
- if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid)
- error (1, errno, "waiting for gunzip process %ld",
- (long) gzip_pid);
- else if (gzip_status != 0)
- error (1, 0, "gunzip exited %d", gzip_status);
- }
- }
-
- static void
- serve_modified (arg)
- char *arg;
- {
- int size;
- char *size_text;
- char *mode_text;
-
- int gzipped = 0;
-
- if (error_pending ()) return;
-
- mode_text = read_line (stdin);
- if (mode_text == NULL)
- {
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
- {
- if (feof (stdin))
- sprintf (pending_error_text,
- "E end of file reading mode for %s", arg);
- else
- {
- sprintf (pending_error_text,
- "E error reading mode for %s", arg);
- pending_error = errno;
- }
- }
- else
- pending_error = ENOMEM;
- return;
- }
- else if (mode_text == NO_MEM_ERROR)
- {
- pending_error = ENOMEM;
- return;
- }
- size_text = read_line (stdin);
- if (size_text == NULL)
- {
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
- {
- if (feof (stdin))
- sprintf (pending_error_text,
- "E end of file reading size for %s", arg);
- else
- {
- sprintf (pending_error_text,
- "E error reading size for %s", arg);
- pending_error = errno;
- }
- }
- else
- pending_error = ENOMEM;
- return;
- }
- else if (size_text == NO_MEM_ERROR)
- {
- pending_error = ENOMEM;
- return;
- }
- if (size_text[0] == 'z')
- {
- gzipped = 1;
- size = atoi (size_text + 1);
- }
- else
- size = atoi (size_text);
- free (size_text);
-
- if (size >= 0)
- {
- receive_file (size, arg, gzipped);
- if (error_pending ()) return;
- }
-
- {
- int status = change_mode (arg, mode_text);
- free (mode_text);
- if (status)
- {
- pending_error_text = malloc (40 + strlen (arg));
- if (pending_error_text)
- sprintf (pending_error_text,
- "E cannot change mode for %s", arg);
- pending_error = status;
- return;
- }
- }
- }
-
- #endif /* SERVER_SUPPORT */
-
- #if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
-
- int use_unchanged = 0;
-
- #endif
- #ifdef SERVER_SUPPORT
-
- static void
- serve_enable_unchanged (arg)
- char *arg;
- {
- use_unchanged = 1;
- }
-
- static void
- serve_lost (arg)
- char *arg;
- {
- if (use_unchanged)
- {
- /* A missing file already indicates it is nonexistent. */
- return;
- }
- else
- {
- struct utimbuf ut;
- int fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0666);
- if (fd < 0 || close (fd) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(arg));
- sprintf(pending_error_text, "E cannot open %s", arg);
- return;
- }
- /*
- * Set the times to the beginning of the epoch to tell time_stamp()
- * that the file was lost.
- */
- ut.actime = 0;
- ut.modtime = 0;
- if (utime (arg, &ut) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(arg));
- sprintf(pending_error_text, "E cannot utime %s", arg);
- return;
- }
- }
- }
-
- struct an_entry {
- struct an_entry *next;
- char *entry;
- };
-
- static struct an_entry *entries;
-
- static void
- serve_unchanged (arg)
- char *arg;
- {
- if (error_pending ())
- return;
- if (!use_unchanged)
- {
- /* A missing file already indicates it is unchanged. */
- return;
- }
- else
- {
- struct an_entry *p;
- char *name;
- char *cp;
- char *timefield;
-
- /* Rewrite entries file to have `=' in timestamp field. */
- for (p = entries; p != NULL; p = p->next)
- {
- name = p->entry + 1;
- cp = strchr (name, '/');
- if (cp != NULL
- && strlen (arg) == cp - name
- && strncmp (arg, name, cp - name) == 0)
- {
- timefield = strchr (cp + 1, '/') + 1;
- if (*timefield != '=')
- {
- cp = timefield + strlen (timefield);
- cp[1] = '\0';
- while (cp > timefield)
- {
- *cp = cp[-1];
- --cp;
- }
- *timefield = '=';
- }
- break;
- }
- }
- }
- }
-
- static void
- serve_entry (arg)
- char *arg;
- {
- struct an_entry *p;
- char *cp;
- if (error_pending()) return;
- p = (struct an_entry *) malloc (sizeof (struct an_entry));
- if (p == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- /* Leave space for serve_unchanged to write '=' if it wants. */
- cp = malloc (strlen (arg) + 2);
- if (cp == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (cp, arg);
- p->next = entries;
- p->entry = cp;
- entries = p;
- }
-
- static void
- server_write_entries ()
- {
- FILE *f;
- struct an_entry *p;
- struct an_entry *q;
-
- if (entries == NULL)
- return;
-
- f = NULL;
- /* Note that we free all the entries regardless of errors. */
- if (!error_pending ())
- {
- f = fopen (CVSADM_ENT, "w");
- if (f == NULL)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT);
- }
- }
- for (p = entries; p != NULL;)
- {
- if (!error_pending ())
- {
- if (fprintf (f, "%s\n", p->entry) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_ENT);
- }
- }
- free (p->entry);
- q = p->next;
- free (p);
- p = q;
- }
- entries = NULL;
- if (f != NULL && fclose (f) == EOF && !error_pending ())
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
- }
- }
-
- struct notify_note {
- /* Directory in which this notification happens. malloc'd*/
- char *dir;
-
- /* malloc'd. */
- char *filename;
-
- /* The following three all in one malloc'd block, pointed to by TYPE.
- Each '\0' terminated. */
- /* "E" or "U". */
- char *type;
- /* time+host+dir */
- char *val;
- char *watches;
-
- struct notify_note *next;
- };
-
- static struct notify_note *notify_list;
- /* Used while building list, to point to the last node that already exists. */
- static struct notify_note *last_node;
-
- static void serve_notify PROTO ((char *));
-
- static void
- serve_notify (arg)
- char *arg;
- {
- struct notify_note *new;
- char *data;
-
- if (error_pending ()) return;
-
- new = (struct notify_note *) malloc (sizeof (struct notify_note));
- if (new == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- if (dir_name == NULL)
- goto error;
- new->dir = malloc (strlen (dir_name) + 1);
- if (new->dir == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (new->dir, dir_name);
- new->filename = malloc (strlen (arg) + 1);
- if (new->filename == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (new->filename, arg);
-
- data = read_line (stdin);
- if (data == NULL)
- {
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
- {
- if (feof (stdin))
- sprintf (pending_error_text,
- "E end of file reading mode for %s", arg);
- else
- {
- sprintf (pending_error_text,
- "E error reading mode for %s", arg);
- pending_error = errno;
- }
- }
- else
- pending_error = ENOMEM;
- }
- else if (data == NO_MEM_ERROR)
- {
- pending_error = ENOMEM;
- }
- else
- {
- char *cp;
-
- new->type = data;
- if (data[1] != '\t')
- goto error;
- data[1] = '\0';
- cp = data + 2;
- new->val = cp;
- cp = strchr (cp, '\t');
- if (cp == NULL)
- goto error;
- *cp++ = '+';
- cp = strchr (cp, '\t');
- if (cp == NULL)
- goto error;
- *cp++ = '+';
- cp = strchr (cp, '\t');
- if (cp == NULL)
- goto error;
- *cp++ = '\0';
- new->watches = cp;
- /* If there is another tab, ignore everything after it,
- for future expansion. */
- cp = strchr (cp, '\t');
- if (cp != NULL)
- {
- *cp = '\0';
- }
-
- new->next = NULL;
-
- if (last_node == NULL)
- {
- notify_list = new;
- }
- else
- last_node->next = new;
- last_node = new;
- }
- return;
- error:
- pending_error_text = malloc (40);
- if (pending_error_text)
- strcpy (pending_error_text,
- "E Protocol error; misformed Notify request");
- pending_error = 0;
- return;
- }
-
- /* Process all the Notify requests that we have stored up. Returns 0
- if successful, if not prints error message (via error()) and
- returns negative value. */
- static int
- server_notify ()
- {
- struct notify_note *p;
- char *repos;
- List *list;
- Node *node;
- int status;
-
- while (notify_list != NULL)
- {
- if (chdir (notify_list->dir) < 0)
- {
- error (0, errno, "cannot change to %s", notify_list->dir);
- return -1;
- }
- repos = Name_Repository (NULL, NULL);
-
- /* Now writelock. */
- list = getlist ();
- node = getnode ();
- node->type = LOCK;
- node->key = xstrdup (repos);
- status = addnode (list, node);
- assert (status == 0);
- Writer_Lock (list);
-
- fileattr_startdir (repos);
-
- notify_do (*notify_list->type, notify_list->filename, getcaller(),
- notify_list->val, notify_list->watches, repos);
-
- printf ("Notified ");
- if (use_dir_and_repos)
- {
- char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
- if (dir[0] == '\0')
- fputs (".", stdout);
- else
- fputs (dir, stdout);
- fputs ("/\n", stdout);
- }
- fputs (repos, stdout);
- fputs ("/", stdout);
- fputs (notify_list->filename, stdout);
- fputs ("\n", stdout);
-
- p = notify_list->next;
- free (notify_list->filename);
- free (notify_list->dir);
- free (notify_list->type);
- free (notify_list);
- notify_list = p;
-
- fileattr_write ();
- fileattr_free ();
-
- /* Remove the writelock. */
- Lock_Cleanup ();
- dellist (&list);
- }
- /* do_cvs_command writes to stdout via write(), not stdio, so better
- flush out the buffer. */
- fflush (stdout);
- return 0;
- }
-
- static int argument_count;
- static char **argument_vector;
- static int argument_vector_size;
-
- static void
- serve_argument (arg)
- char *arg;
- {
- char *p;
-
- if (error_pending()) return;
-
- if (argument_vector_size <= argument_count)
- {
- argument_vector_size *= 2;
- argument_vector =
- (char **) realloc ((char *)argument_vector,
- argument_vector_size * sizeof (char *));
- if (argument_vector == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- }
- p = malloc (strlen (arg) + 1);
- if (p == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (p, arg);
- argument_vector[argument_count++] = p;
- }
-
- static void
- serve_argumentx (arg)
- char *arg;
- {
- char *p;
-
- if (error_pending()) return;
-
- p = argument_vector[argument_count - 1];
- p = realloc (p, strlen (p) + 1 + strlen (arg) + 1);
- if (p == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcat (p, "\n");
- strcat (p, arg);
- argument_vector[argument_count - 1] = p;
- }
-
- static void
- serve_global_option (arg)
- char *arg;
- {
- if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0')
- {
- error_return:
- pending_error_text = malloc (strlen (arg) + 80);
- sprintf (pending_error_text, "E Protocol error: bad global option %s",
- arg);
- return;
- }
- switch (arg[1])
- {
- case 'n':
- noexec = 1;
- break;
- case 'q':
- quiet = 1;
- break;
- case 'r':
- cvswrite = 0;
- break;
- case 'Q':
- really_quiet = 1;
- break;
- case 'l':
- logoff = 1;
- break;
- case 't':
- trace = 1;
- break;
- default:
- goto error_return;
- }
- }
-
- static void
- serve_set (arg)
- char *arg;
- {
- /* FIXME: This sends errors immediately (I think); they should be
- put into pending_error. */
- variable_set (arg);
- }
-
- /*
- * We must read data from a child process and send it across the
- * network. We do not want to block on writing to the network, so we
- * store the data from the child process in memory. A BUFFER
- * structure holds the status of one communication, and uses a linked
- * list of buffer_data structures to hold data.
- */
-
- struct buffer
- {
- /* Data. */
- struct buffer_data *data;
-
- /* Last buffer on data chain. */
- struct buffer_data *last;
-
- /* File descriptor to write to or read from. */
- int fd;
-
- /* Nonzero if this is an output buffer (sanity check). */
- int output;
-
- /* Nonzero if the file descriptor is in nonblocking mode. */
- int nonblocking;
-
- /* Function to call if we can't allocate memory. */
- void (*memory_error) PROTO((struct buffer *));
- };
-
- /* Data is stored in lists of these structures. */
-
- struct buffer_data
- {
- /* Next buffer in linked list. */
- struct buffer_data *next;
-
- /*
- * A pointer into the data area pointed to by the text field. This
- * is where to find data that has not yet been written out.
- */
- char *bufp;
-
- /* The number of data bytes found at BUFP. */
- int size;
-
- /*
- * Actual buffer. This never changes after the structure is
- * allocated. The buffer is BUFFER_DATA_SIZE bytes.
- */
- char *text;
- };
-
- /* The size we allocate for each buffer_data structure. */
- #define BUFFER_DATA_SIZE (4096)
-
- #ifdef SERVER_FLOWCONTROL
- /* The maximum we'll queue to the remote client before blocking. */
- # ifndef SERVER_HI_WATER
- # define SERVER_HI_WATER (2 * 1024 * 1024)
- # endif /* SERVER_HI_WATER */
- /* When the buffer drops to this, we restart the child */
- # ifndef SERVER_LO_WATER
- # define SERVER_LO_WATER (1 * 1024 * 1024)
- # endif /* SERVER_LO_WATER */
- #endif /* SERVER_FLOWCONTROL */
-
- /* Linked list of available buffer_data structures. */
- static struct buffer_data *free_buffer_data;
-
- static void allocate_buffer_datas PROTO((void));
- static inline struct buffer_data *get_buffer_data PROTO((void));
- static int buf_empty_p PROTO((struct buffer *));
- static void buf_output PROTO((struct buffer *, const char *, int));
- static void buf_output0 PROTO((struct buffer *, const char *));
- static inline void buf_append_char PROTO((struct buffer *, int));
- static int buf_send_output PROTO((struct buffer *));
- static int set_nonblock PROTO((struct buffer *));
- static int set_block PROTO((struct buffer *));
- static int buf_send_counted PROTO((struct buffer *));
- static inline void buf_append_data PROTO((struct buffer *,
- struct buffer_data *,
- struct buffer_data *));
- static int buf_read_file PROTO((FILE *, long, struct buffer_data **,
- struct buffer_data **));
- static int buf_input_data PROTO((struct buffer *, int *));
- static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int));
- static int buf_copy_counted PROTO((struct buffer *, struct buffer *));
-
- #ifdef SERVER_FLOWCONTROL
- static int buf_count_mem PROTO((struct buffer *));
- static int set_nonblock_fd PROTO((int));
- #endif /* SERVER_FLOWCONTROL */
-
- /* Allocate more buffer_data structures. */
-
- static void
- allocate_buffer_datas ()
- {
- struct buffer_data *alc;
- char *space;
- int i;
-
- /* Allocate buffer_data structures in blocks of 16. */
- #define ALLOC_COUNT (16)
-
- alc = ((struct buffer_data *)
- malloc (ALLOC_COUNT * sizeof (struct buffer_data)));
- space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE);
- if (alc == NULL || space == NULL)
- return;
- for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE)
- {
- alc->next = free_buffer_data;
- free_buffer_data = alc;
- alc->text = space;
- }
- }
-
- /* Get a new buffer_data structure. */
-
- static inline struct buffer_data *
- get_buffer_data ()
- {
- struct buffer_data *ret;
-
- if (free_buffer_data == NULL)
- {
- allocate_buffer_datas ();
- if (free_buffer_data == NULL)
- return NULL;
- }
-
- ret = free_buffer_data;
- free_buffer_data = ret->next;
- return ret;
- }
-
- /* See whether a buffer is empty. */
-
- static int
- buf_empty_p (buf)
- struct buffer *buf;
- {
- struct buffer_data *data;
-
- for (data = buf->data; data != NULL; data = data->next)
- if (data->size > 0)
- return 0;
- return 1;
- }
-
- #ifdef SERVER_FLOWCONTROL
- /*
- * Count how much data is stored in the buffer..
- * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE.
- */
-
- static int
- buf_count_mem (buf)
- struct buffer *buf;
- {
- struct buffer_data *data;
- int mem = 0;
-
- for (data = buf->data; data != NULL; data = data->next)
- mem += BUFFER_DATA_SIZE;
-
- return mem;
- }
- #endif /* SERVER_FLOWCONTROL */
-
- /* Add data DATA of length LEN to BUF. */
-
- static void
- buf_output (buf, data, len)
- struct buffer *buf;
- const char *data;
- int len;
- {
- if (buf->data != NULL
- && (((buf->last->text + BUFFER_DATA_SIZE)
- - (buf->last->bufp + buf->last->size))
- >= len))
- {
- memcpy (buf->last->bufp + buf->last->size, data, len);
- buf->last->size += len;
- return;
- }
-
- while (1)
- {
- struct buffer_data *newdata;
-
- newdata = get_buffer_data ();
- if (newdata == NULL)
- {
- (*buf->memory_error) (buf);
- return;
- }
-
- if (buf->data == NULL)
- buf->data = newdata;
- else
- buf->last->next = newdata;
- newdata->next = NULL;
- buf->last = newdata;
-
- newdata->bufp = newdata->text;
-
- if (len <= BUFFER_DATA_SIZE)
- {
- newdata->size = len;
- memcpy (newdata->text, data, len);
- return;
- }
-
- newdata->size = BUFFER_DATA_SIZE;
- memcpy (newdata->text, data, BUFFER_DATA_SIZE);
-
- data += BUFFER_DATA_SIZE;
- len -= BUFFER_DATA_SIZE;
- }
-
- /*NOTREACHED*/
- }
-
- /* Add a '\0' terminated string to BUF. */
-
- static void
- buf_output0 (buf, string)
- struct buffer *buf;
- const char *string;
- {
- buf_output (buf, string, strlen (string));
- }
-
- /* Add a single character to BUF. */
-
- static inline void
- buf_append_char (buf, ch)
- struct buffer *buf;
- int ch;
- {
- if (buf->data != NULL
- && (buf->last->text + BUFFER_DATA_SIZE
- != buf->last->bufp + buf->last->size))
- {
- *(buf->last->bufp + buf->last->size) = ch;
- ++buf->last->size;
- }
- else
- {
- char b;
-
- b = ch;
- buf_output (buf, &b, 1);
- }
- }
-
- /*
- * Send all the output we've been saving up. Returns 0 for success or
- * errno code. If the buffer has been set to be nonblocking, this
- * will just write until the write would block.
- */
-
- static int
- buf_send_output (buf)
- struct buffer *buf;
- {
- if (! buf->output)
- abort ();
-
- while (buf->data != NULL)
- {
- struct buffer_data *data;
-
- data = buf->data;
- while (data->size > 0)
- {
- int nbytes;
-
- nbytes = write (buf->fd, data->bufp, data->size);
- if (nbytes <= 0)
- {
- int status;
-
- if (buf->nonblocking
- && (nbytes == 0
- #ifdef EWOULDBLOCK
- || errno == EWOULDBLOCK
- #endif
- || errno == EAGAIN))
- {
- /*
- * A nonblocking write failed to write any data.
- * Just return.
- */
- return 0;
- }
-
- /*
- * An error, or EOF. Throw away all the data and
- * return.
- */
- if (nbytes == 0)
- status = EIO;
- else
- status = errno;
-
- buf->last->next = free_buffer_data;
- free_buffer_data = buf->data;
- buf->data = NULL;
- buf->last = NULL;
-
- return status;
- }
-
- data->size -= nbytes;
- data->bufp += nbytes;
- }
-
- buf->data = data->next;
- data->next = free_buffer_data;
- free_buffer_data = data;
- }
-
- buf->last = NULL;
-
- return 0;
- }
-
- #ifdef SERVER_FLOWCONTROL
- /*
- * Set buffer BUF to non-blocking I/O. Returns 0 for success or errno
- * code.
- */
-
- static int
- set_nonblock_fd (fd)
- int fd;
- {
- int flags;
-
- flags = fcntl (fd, F_GETFL, 0);
- if (flags < 0)
- return errno;
- if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
- return errno;
- return 0;
- }
- #endif /* SERVER_FLOWCONTROL */
-
- static int
- set_nonblock (buf)
- struct buffer *buf;
- {
- int flags;
-
- if (buf->nonblocking)
- return 0;
- flags = fcntl (buf->fd, F_GETFL, 0);
- if (flags < 0)
- return errno;
- if (fcntl (buf->fd, F_SETFL, flags | O_NONBLOCK) < 0)
- return errno;
- buf->nonblocking = 1;
- return 0;
- }
-
- /*
- * Set buffer BUF to blocking I/O. Returns 0 for success or errno
- * code.
- */
-
- static int
- set_block (buf)
- struct buffer *buf;
- {
- int flags;
-
- if (! buf->nonblocking)
- return 0;
- flags = fcntl (buf->fd, F_GETFL, 0);
- if (flags < 0)
- return errno;
- if (fcntl (buf->fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
- return errno;
- buf->nonblocking = 0;
- return 0;
- }
-
- /*
- * Send a character count and some output. Returns errno code or 0 for
- * success.
- *
- * Sending the count in binary is OK since this is only used on a pipe
- * within the same system.
- */
-
- static int
- buf_send_counted (buf)
- struct buffer *buf;
- {
- int size;
- struct buffer_data *data;
-
- if (! buf->output)
- abort ();
-
- size = 0;
- for (data = buf->data; data != NULL; data = data->next)
- size += data->size;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- (*buf->memory_error) (buf);
- return ENOMEM;
- }
-
- data->next = buf->data;
- buf->data = data;
- if (buf->last == NULL)
- buf->last = data;
-
- data->bufp = data->text;
- data->size = sizeof (int);
-
- *((int *) data->text) = size;
-
- return buf_send_output (buf);
- }
-
- /* Append a list of buffer_data structures to an buffer. */
-
- static inline void
- buf_append_data (buf, data, last)
- struct buffer *buf;
- struct buffer_data *data;
- struct buffer_data *last;
- {
- if (data != NULL)
- {
- if (buf->data == NULL)
- buf->data = data;
- else
- buf->last->next = data;
- buf->last = last;
- }
- }
-
- /*
- * Copy the contents of file F into buffer_data structures. We can't
- * copy directly into an buffer, because we want to handle failure and
- * succeess differently. Returns 0 on success, or -2 if out of
- * memory, or a status code on error. Since the caller happens to
- * know the size of the file, it is passed in as SIZE. On success,
- * this function sets *RETP and *LASTP, which may be passed to
- * buf_append_data.
- */
-
- static int
- buf_read_file (f, size, retp, lastp)
- FILE *f;
- long size;
- struct buffer_data **retp;
- struct buffer_data **lastp;
- {
- int status;
-
- *retp = NULL;
- *lastp = NULL;
-
- while (size > 0)
- {
- struct buffer_data *data;
- int get;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- status = -2;
- goto error_return;
- }
-
- if (*retp == NULL)
- *retp = data;
- else
- (*lastp)->next = data;
- data->next = NULL;
- *lastp = data;
-
- data->bufp = data->text;
- data->size = 0;
-
- if (size > BUFFER_DATA_SIZE)
- get = BUFFER_DATA_SIZE;
- else
- get = size;
-
- errno = EIO;
- if (fread (data->text, get, 1, f) != 1)
- {
- status = errno;
- goto error_return;
- }
-
- data->size += get;
- size -= get;
- }
-
- return 0;
-
- error_return:
- if (*retp != NULL)
- {
- (*lastp)->next = free_buffer_data;
- free_buffer_data = *retp;
- }
- return status;
- }
-
- static int
- buf_read_file_to_eof (f, retp, lastp)
- FILE *f;
- struct buffer_data **retp;
- struct buffer_data **lastp;
- {
- int status;
-
- *retp = NULL;
- *lastp = NULL;
-
- while (!feof (f))
- {
- struct buffer_data *data;
- int get, nread;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- status = -2;
- goto error_return;
- }
-
- if (*retp == NULL)
- *retp = data;
- else
- (*lastp)->next = data;
- data->next = NULL;
- *lastp = data;
-
- data->bufp = data->text;
- data->size = 0;
-
- get = BUFFER_DATA_SIZE;
-
- errno = EIO;
- nread = fread (data->text, 1, get, f);
- if (nread == 0 && !feof (f))
- {
- status = errno;
- goto error_return;
- }
-
- data->size = nread;
- }
-
- return 0;
-
- error_return:
- if (*retp != NULL)
- {
- (*lastp)->next = free_buffer_data;
- free_buffer_data = *retp;
- }
- return status;
- }
-
- static int
- buf_chain_length (buf)
- struct buffer_data *buf;
- {
- int size = 0;
- while (buf)
- {
- size += buf->size;
- buf = buf->next;
- }
- return size;
- }
-
- /*
- * Read an arbitrary amount of data from a file descriptor into an
- * input buffer. The file descriptor will be in nonblocking mode, and
- * we just grab what we can. Return 0 on success, or -1 on end of
- * file, or -2 if out of memory, or an error code. If COUNTP is not
- * NULL, *COUNTP is set to the number of bytes read.
- */
-
- static int
- buf_input_data (buf, countp)
- struct buffer *buf;
- int *countp;
- {
- if (buf->output)
- abort ();
-
- if (countp != NULL)
- *countp = 0;
-
- while (1)
- {
- int get;
- int nbytes;
-
- if (buf->data == NULL
- || (buf->last->bufp + buf->last->size
- == buf->last->text + BUFFER_DATA_SIZE))
- {
- struct buffer_data *data;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- (*buf->memory_error) (buf);
- return -2;
- }
-
- if (buf->data == NULL)
- buf->data = data;
- else
- buf->last->next = data;
- data->next = NULL;
- buf->last = data;
-
- data->bufp = data->text;
- data->size = 0;
- }
-
- get = ((buf->last->text + BUFFER_DATA_SIZE)
- - (buf->last->bufp + buf->last->size));
- nbytes = read (buf->fd, buf->last->bufp + buf->last->size, get);
- if (nbytes <= 0)
- {
- if (nbytes == 0)
- {
- /*
- * This assumes that we are using POSIX or BSD style
- * nonblocking I/O. On System V we will get a zero
- * return if there is no data, even when not at EOF.
- */
- return -1;
- }
-
- if (errno == EAGAIN
- #ifdef EWOULDBLOCK
- || errno == EWOULDBLOCK
- #endif
- )
- return 0;
-
- return errno;
- }
-
- buf->last->size += nbytes;
- if (countp != NULL)
- *countp += nbytes;
- }
-
- /*NOTREACHED*/
- }
-
- /*
- * Copy lines from an input buffer to an output buffer. This copies
- * all complete lines (characters up to a newline) from INBUF to
- * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND
- * and a space.
- */
-
- static void
- buf_copy_lines (outbuf, inbuf, command)
- struct buffer *outbuf;
- struct buffer *inbuf;
- int command;
- {
- if (! outbuf->output || inbuf->output)
- abort ();
-
- while (1)
- {
- struct buffer_data *data;
- struct buffer_data *nldata;
- char *nl;
- int len;
-
- /* See if there is a newline in INBUF. */
- nldata = NULL;
- nl = NULL;
- for (data = inbuf->data; data != NULL; data = data->next)
- {
- nl = memchr (data->bufp, '\n', data->size);
- if (nl != NULL)
- {
- nldata = data;
- break;
- }
- }
-
- if (nldata == NULL)
- {
- /* There are no more lines in INBUF. */
- return;
- }
-
- /* Put in the command. */
- buf_append_char (outbuf, command);
- buf_append_char (outbuf, ' ');
-
- if (inbuf->data != nldata)
- {
- /*
- * Simply move over all the buffers up to the one containing
- * the newline.
- */
- for (data = inbuf->data; data->next != nldata; data = data->next)
- ;
- data->next = NULL;
- buf_append_data (outbuf, inbuf->data, data);
- inbuf->data = nldata;
- }
-
- /*
- * If the newline is at the very end of the buffer, just move
- * the buffer onto OUTBUF. Otherwise we must copy the data.
- */
- len = nl + 1 - nldata->bufp;
- if (len == nldata->size)
- {
- inbuf->data = nldata->next;
- if (inbuf->data == NULL)
- inbuf->last = NULL;
-
- nldata->next = NULL;
- buf_append_data (outbuf, nldata, nldata);
- }
- else
- {
- buf_output (outbuf, nldata->bufp, len);
- nldata->bufp += len;
- nldata->size -= len;
- }
- }
- }
-
- /*
- * Copy counted data from one buffer to another. The count is an
- * integer, host size, host byte order (it is only used across a
- * pipe). If there is enough data, it should be moved over. If there
- * is not enough data, it should remain on the original buffer. This
- * returns the number of bytes it needs to see in order to actually
- * copy something over.
- */
-
- static int
- buf_copy_counted (outbuf, inbuf)
- struct buffer *outbuf;
- struct buffer *inbuf;
- {
- if (! outbuf->output || inbuf->output)
- abort ();
-
- while (1)
- {
- struct buffer_data *data;
- int need;
- union
- {
- char intbuf[sizeof (int)];
- int i;
- } u;
- char *intp;
- int count;
- struct buffer_data *start;
- int startoff;
- struct buffer_data *stop;
- int stopwant;
-
- /* See if we have enough bytes to figure out the count. */
- need = sizeof (int);
- intp = u.intbuf;
- for (data = inbuf->data; data != NULL; data = data->next)
- {
- if (data->size >= need)
- {
- memcpy (intp, data->bufp, need);
- break;
- }
- memcpy (intp, data->bufp, data->size);
- intp += data->size;
- need -= data->size;
- }
- if (data == NULL)
- {
- /* We don't have enough bytes to form an integer. */
- return need;
- }
-
- count = u.i;
- start = data;
- startoff = need;
-
- /*
- * We have an integer in COUNT. We have gotten all the data
- * from INBUF in all buffers before START, and we have gotten
- * STARTOFF bytes from START. See if we have enough bytes
- * remaining in INBUF.
- */
- need = count - (start->size - startoff);
- if (need <= 0)
- {
- stop = start;
- stopwant = count;
- }
- else
- {
- for (data = start->next; data != NULL; data = data->next)
- {
- if (need <= data->size)
- break;
- need -= data->size;
- }
- if (data == NULL)
- {
- /* We don't have enough bytes. */
- return need;
- }
- stop = data;
- stopwant = need;
- }
-
- /*
- * We have enough bytes. Free any buffers in INBUF before
- * START, and remove STARTOFF bytes from START, so that we can
- * forget about STARTOFF.
- */
- start->bufp += startoff;
- start->size -= startoff;
-
- if (start->size == 0)
- start = start->next;
-
- if (stop->size == stopwant)
- {
- stop = stop->next;
- stopwant = 0;
- }
-
- while (inbuf->data != start)
- {
- data = inbuf->data;
- inbuf->data = data->next;
- data->next = free_buffer_data;
- free_buffer_data = data;
- }
-
- /*
- * We want to copy over the bytes from START through STOP. We
- * only want STOPWANT bytes from STOP.
- */
-
- if (start != stop)
- {
- /* Attach the buffers from START through STOP to OUTBUF. */
- for (data = start; data->next != stop; data = data->next)
- ;
- inbuf->data = stop;
- data->next = NULL;
- buf_append_data (outbuf, start, data);
- }
-
- if (stopwant > 0)
- {
- buf_output (outbuf, stop->bufp, stopwant);
- stop->bufp += stopwant;
- stop->size -= stopwant;
- }
- }
-
- /*NOTREACHED*/
- }
-
- /* While processing requests, this buffer accumulates data to be sent to
- the client, and then once we are in do_cvs_command, we use it
- for all the data to be sent. */
- static struct buffer buf_to_net;
-
- static void serve_questionable PROTO((char *));
-
- static void
- serve_questionable (arg)
- char *arg;
- {
- static int initted;
-
- if (!initted)
- {
- /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server,
- and CVSIGNORE on server. */
- ign_setup ();
- initted = 1;
- }
-
- if (dir_name == NULL)
- {
- buf_output0 (&buf_to_net, "E Protocol error: 'Directory' missing");
- return;
- }
-
- if (!ign_name (arg))
- {
- char *update_dir;
-
- buf_output (&buf_to_net, "M ? ", 4);
- update_dir = dir_name + strlen (server_temp_dir) + 1;
- if (!(update_dir[0] == '.' && update_dir[1] == '\0'))
- {
- buf_output0 (&buf_to_net, update_dir);
- buf_output (&buf_to_net, "/", 1);
- }
- buf_output0 (&buf_to_net, arg);
- buf_output (&buf_to_net, "\n", 1);
- }
- }
-
- static void serve_case PROTO ((char *));
-
- static void
- serve_case (arg)
- char *arg;
- {
- ign_case = 1;
- }
-
- static struct buffer protocol;
-
- /* This is the output which we are saving up to send to the server, in the
- child process. We will push it through, via the `protocol' buffer, when
- we have a complete line. */
- static struct buffer saved_output;
- /* Likewise, but stuff which will go to stderr. */
- static struct buffer saved_outerr;
-
- static void
- protocol_memory_error (buf)
- struct buffer *buf;
- {
- error (1, ENOMEM, "Virtual memory exhausted");
- }
-
- /*
- * Process IDs of the subprocess, or negative if that subprocess
- * does not exist.
- */
- static pid_t command_pid;
-
- static void
- outbuf_memory_error (buf)
- struct buffer *buf;
- {
- static const char msg[] = "E Fatal server error\n\
- error ENOMEM Virtual memory exhausted.\n";
- if (command_pid > 0)
- kill (command_pid, SIGTERM);
-
- /*
- * We have arranged things so that printing this now either will
- * be legal, or the "E fatal error" line will get glommed onto the
- * end of an existing "E" or "M" response.
- */
-
- /* If this gives an error, not much we could do. syslog() it? */
- write (STDOUT_FILENO, msg, sizeof (msg) - 1);
- server_cleanup (0);
- exit (EXIT_FAILURE);
- }
-
- static void
- input_memory_error (buf)
- struct buffer *buf;
- {
- outbuf_memory_error (buf);
- }
-
- /* Execute COMMAND in a subprocess with the approriate funky things done. */
-
- static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain;
- static int max_command_fd;
-
- #ifdef SERVER_FLOWCONTROL
- static int flowcontrol_pipe[2];
- #endif /* SERVER_FLOWCONTROL */
-
- static void
- do_cvs_command (command)
- int (*command) PROTO((int argc, char **argv));
- {
- /*
- * The following file descriptors are set to -1 if that file is not
- * currently open.
- */
-
- /* Data on these pipes is a series of '\n'-terminated lines. */
- int stdout_pipe[2];
- int stderr_pipe[2];
-
- /*
- * Data on this pipe is a series of counted (see buf_send_counted)
- * packets. Each packet must be processed atomically (i.e. not
- * interleaved with data from stdout_pipe or stderr_pipe).
- */
- int protocol_pipe[2];
-
- int dev_null_fd = -1;
-
- int errs;
-
- command_pid = -1;
- stdout_pipe[0] = -1;
- stdout_pipe[1] = -1;
- stderr_pipe[0] = -1;
- stderr_pipe[1] = -1;
- protocol_pipe[0] = -1;
- protocol_pipe[1] = -1;
-
- server_write_entries ();
-
- if (print_pending_error ())
- goto free_args_and_return;
-
- (void) server_notify ();
-
- /*
- * We use a child process which actually does the operation. This
- * is so we can intercept its standard output. Even if all of CVS
- * were written to go to some special routine instead of writing
- * to stdout or stderr, we would still need to do the same thing
- * for the RCS commands.
- */
-
- if (pipe (stdout_pipe) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- if (pipe (stderr_pipe) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- if (pipe (protocol_pipe) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- #ifdef SERVER_FLOWCONTROL
- if (pipe (flowcontrol_pipe) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- set_nonblock_fd (flowcontrol_pipe[0]);
- set_nonblock_fd (flowcontrol_pipe[1]);
- #endif /* SERVER_FLOWCONTROL */
-
- dev_null_fd = open ("/dev/null", O_RDONLY);
- if (dev_null_fd < 0)
- {
- print_error (errno);
- goto error_exit;
- }
-
- /* Don't use vfork; we're not going to exec(). */
- command_pid = fork ();
- if (command_pid < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- if (command_pid == 0)
- {
- int exitstatus;
-
- /* Since we're in the child, and the parent is going to take
- care of packaging up our error messages, we can clear this
- flag. */
- error_use_protocol = 0;
-
- protocol.data = protocol.last = NULL;
- protocol.fd = protocol_pipe[1];
- protocol.output = 1;
- protocol.nonblocking = 0;
- protocol.memory_error = protocol_memory_error;
-
- saved_output.data = saved_output.last = NULL;
- saved_output.fd = -1;
- saved_output.output = 0;
- saved_output.nonblocking = 0;
- saved_output.memory_error = protocol_memory_error;
- saved_outerr = saved_output;
-
- if (dup2 (dev_null_fd, STDIN_FILENO) < 0)
- error (1, errno, "can't set up pipes");
- if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0)
- error (1, errno, "can't set up pipes");
- if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0)
- error (1, errno, "can't set up pipes");
- close (stdout_pipe[0]);
- close (stderr_pipe[0]);
- close (protocol_pipe[0]);
- #ifdef SERVER_FLOWCONTROL
- close (flowcontrol_pipe[1]);
- #endif /* SERVER_FLOWCONTROL */
-
- /*
- * Set this in .bashrc if you want to give yourself time to attach
- * to the subprocess with a debugger.
- */
- if (getenv ("CVS_SERVER_SLEEP"))
- {
- int secs = atoi (getenv ("CVS_SERVER_SLEEP"));
- sleep (secs);
- }
-
- exitstatus = (*command) (argument_count, argument_vector);
-
- /*
- * When we exit, that will close the pipes, giving an EOF to
- * the parent.
- */
- exit (exitstatus);
- }
-
- /* OK, sit around getting all the input from the child. */
- {
- struct buffer stdoutbuf;
- struct buffer stderrbuf;
- struct buffer protocol_inbuf;
- /* Number of file descriptors to check in select (). */
- int num_to_check;
- int count_needed = 0;
- #ifdef SERVER_FLOWCONTROL
- int have_flowcontrolled = 0;
- #endif /* SERVER_FLOWCONTROL */
-
- FD_ZERO (&command_fds_to_drain.fds);
- num_to_check = stdout_pipe[0];
- FD_SET (stdout_pipe[0], &command_fds_to_drain.fds);
- if (stderr_pipe[0] > num_to_check)
- num_to_check = stderr_pipe[0];
- FD_SET (stderr_pipe[0], &command_fds_to_drain.fds);
- if (protocol_pipe[0] > num_to_check)
- num_to_check = protocol_pipe[0];
- FD_SET (protocol_pipe[0], &command_fds_to_drain.fds);
- if (STDOUT_FILENO > num_to_check)
- num_to_check = STDOUT_FILENO;
- max_command_fd = num_to_check;
- /*
- * File descriptors are numbered from 0, so num_to_check needs to
- * be one larger than the largest descriptor.
- */
- ++num_to_check;
- if (num_to_check > FD_SETSIZE)
- {
- printf ("E internal error: FD_SETSIZE not big enough.\nerror \n");
- goto error_exit;
- }
-
- stdoutbuf.data = stdoutbuf.last = NULL;
- stdoutbuf.fd = stdout_pipe[0];
- stdoutbuf.output = 0;
- stdoutbuf.nonblocking = 0;
- stdoutbuf.memory_error = input_memory_error;
-
- stderrbuf.data = stderrbuf.last = NULL;
- stderrbuf.fd = stderr_pipe[0];
- stderrbuf.output = 0;
- stderrbuf.nonblocking = 0;
- stderrbuf.memory_error = input_memory_error;
-
- protocol_inbuf.data = protocol_inbuf.last = NULL;
- protocol_inbuf.fd = protocol_pipe[0];
- protocol_inbuf.output = 0;
- protocol_inbuf.nonblocking = 0;
- protocol_inbuf.memory_error = input_memory_error;
-
- set_nonblock (&buf_to_net);
- set_nonblock (&stdoutbuf);
- set_nonblock (&stderrbuf);
- set_nonblock (&protocol_inbuf);
-
- if (close (stdout_pipe[1]) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- stdout_pipe[1] = -1;
-
- if (close (stderr_pipe[1]) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- stderr_pipe[1] = -1;
-
- if (close (protocol_pipe[1]) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- protocol_pipe[1] = -1;
-
- #ifdef SERVER_FLOWCONTROL
- if (close (flowcontrol_pipe[0]) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- flowcontrol_pipe[0] = -1;
- #endif /* SERVER_FLOWCONTROL */
-
- if (close (dev_null_fd) < 0)
- {
- print_error (errno);
- goto error_exit;
- }
- dev_null_fd = -1;
-
- while (stdout_pipe[0] >= 0
- || stderr_pipe[0] >= 0
- || protocol_pipe[0] >= 0)
- {
- fd_set readfds;
- fd_set writefds;
- int numfds;
- #ifdef SERVER_FLOWCONTROL
- int bufmemsize;
-
- /*
- * See if we are swamping the remote client and filling our VM.
- * Tell child to hold off if we do.
- */
- bufmemsize = buf_count_mem (&buf_to_net);
- if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER))
- {
- if (write(flowcontrol_pipe[1], "S", 1) == 1)
- have_flowcontrolled = 1;
- }
- else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER))
- {
- if (write(flowcontrol_pipe[1], "G", 1) == 1)
- have_flowcontrolled = 0;
- }
- #endif /* SERVER_FLOWCONTROL */
-
- FD_ZERO (&readfds);
- FD_ZERO (&writefds);
- if (! buf_empty_p (&buf_to_net))
- FD_SET (STDOUT_FILENO, &writefds);
-
- if (stdout_pipe[0] >= 0)
- {
- FD_SET (stdout_pipe[0], &readfds);
- }
- if (stderr_pipe[0] >= 0)
- {
- FD_SET (stderr_pipe[0], &readfds);
- }
- if (protocol_pipe[0] >= 0)
- {
- FD_SET (protocol_pipe[0], &readfds);
- }
-
- do {
- /* This used to select on exceptions too, but as far
- as I know there was never any reason to do that and
- SCO doesn't let you select on exceptions on pipes. */
- numfds = select (num_to_check, &readfds, &writefds,
- (fd_set *)0, (struct timeval *)NULL);
- if (numfds < 0
- && errno != EINTR)
- {
- print_error (errno);
- goto error_exit;
- }
- } while (numfds < 0);
-
- if (FD_ISSET (STDOUT_FILENO, &writefds))
- {
- /* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
- }
-
- if (stdout_pipe[0] >= 0
- && (FD_ISSET (stdout_pipe[0], &readfds)))
- {
- int status;
-
- status = buf_input_data (&stdoutbuf, (int *) NULL);
-
- buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
-
- if (status == -1)
- stdout_pipe[0] = -1;
- else if (status > 0)
- {
- print_error (status);
- goto error_exit;
- }
-
- /* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
- }
-
- if (stderr_pipe[0] >= 0
- && (FD_ISSET (stderr_pipe[0], &readfds)))
- {
- int status;
-
- status = buf_input_data (&stderrbuf, (int *) NULL);
-
- buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
-
- if (status == -1)
- stderr_pipe[0] = -1;
- else if (status > 0)
- {
- print_error (status);
- goto error_exit;
- }
-
- /* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
- }
-
- if (protocol_pipe[0] >= 0
- && (FD_ISSET (protocol_pipe[0], &readfds)))
- {
- int status;
- int count_read;
-
- status = buf_input_data (&protocol_inbuf, &count_read);
-
- /*
- * We only call buf_copy_counted if we have read
- * enough bytes to make it worthwhile. This saves us
- * from continually recounting the amount of data we
- * have.
- */
- count_needed -= count_read;
- if (count_needed <= 0)
- count_needed = buf_copy_counted (&buf_to_net,
- &protocol_inbuf);
-
- if (status == -1)
- protocol_pipe[0] = -1;
- else if (status > 0)
- {
- print_error (status);
- goto error_exit;
- }
-
- /* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
- }
- }
-
- /*
- * OK, we've gotten EOF on all the pipes. If there is
- * anything left on stdoutbuf or stderrbuf (this could only
- * happen if there was no trailing newline), send it over.
- */
- if (! buf_empty_p (&stdoutbuf))
- {
- buf_append_char (&stdoutbuf, '\n');
- buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
- }
- if (! buf_empty_p (&stderrbuf))
- {
- buf_append_char (&stderrbuf, '\n');
- buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
- }
- if (! buf_empty_p (&protocol_inbuf))
- buf_output0 (&buf_to_net,
- "E Protocol error: uncounted data discarded\n");
-
- errs = 0;
-
- while (command_pid > 0)
- {
- int status;
- pid_t waited_pid;
- waited_pid = waitpid (command_pid, &status, 0);
- if (waited_pid < 0)
- {
- /*
- * Intentionally ignoring EINTR. Other errors
- * "can't happen".
- */
- continue;
- }
-
- if (WIFEXITED (status))
- errs += WEXITSTATUS (status);
- else
- {
- int sig = WTERMSIG (status);
- /*
- * This is really evil, because signals might be numbered
- * differently on the two systems. We should be using
- * signal names (either of the "Terminated" or the "SIGTERM"
- * variety). But cvs doesn't currently use libiberty...we
- * could roll our own.... FIXME.
- */
- printf ("E Terminated with fatal signal %d\n", sig);
-
- /* Test for a core dump. Is this portable? */
- if (status & 0x80)
- {
- printf ("E Core dumped; preserving %s on server.\n\
- E CVS locks may need cleaning up.\n",
- server_temp_dir);
- dont_delete_temp = 1;
- }
- ++errs;
- }
- if (waited_pid == command_pid)
- command_pid = -1;
- }
-
- /*
- * OK, we've waited for the child. By now all CVS locks are free
- * and it's OK to block on the network.
- */
- set_block (&buf_to_net);
- buf_send_output (&buf_to_net);
- }
-
- if (errs)
- /* We will have printed an error message already. */
- printf ("error \n");
- else
- printf ("ok\n");
- goto free_args_and_return;
-
- error_exit:
- if (command_pid > 0)
- kill (command_pid, SIGTERM);
-
- while (command_pid > 0)
- {
- pid_t waited_pid;
- waited_pid = waitpid (command_pid, (int *) 0, 0);
- if (waited_pid < 0 && errno == EINTR)
- continue;
- if (waited_pid == command_pid)
- command_pid = -1;
- }
-
- close (dev_null_fd);
- close (protocol_pipe[0]);
- close (protocol_pipe[1]);
- close (stderr_pipe[0]);
- close (stderr_pipe[1]);
- close (stdout_pipe[0]);
- close (stdout_pipe[1]);
-
- free_args_and_return:
- /* Now free the arguments. */
- {
- /* argument_vector[0] is a dummy argument, we don't mess with it. */
- char **cp;
- for (cp = argument_vector + 1;
- cp < argument_vector + argument_count;
- ++cp)
- free (*cp);
-
- argument_count = 1;
- }
- return;
- }
-
- #ifdef SERVER_FLOWCONTROL
- /*
- * Called by the child at convenient points in the server's execution for
- * the server child to block.. ie: when it has no locks active.
- */
- void
- server_pause_check()
- {
- int paused = 0;
- char buf[1];
-
- while (read (flowcontrol_pipe[0], buf, 1) == 1)
- {
- if (*buf == 'S') /* Stop */
- paused = 1;
- else if (*buf == 'G') /* Go */
- paused = 0;
- else
- return; /* ??? */
- }
- while (paused) {
- int numfds, numtocheck;
- fd_set fds;
-
- FD_ZERO (&fds);
- FD_SET (flowcontrol_pipe[0], &fds);
- numtocheck = flowcontrol_pipe[0] + 1;
-
- do {
- numfds = select (numtocheck, &fds, (fd_set *)0,
- (fd_set *)0, (struct timeval *)NULL);
- if (numfds < 0
- && errno != EINTR)
- {
- print_error (errno);
- return;
- }
- } while (numfds < 0);
-
- if (FD_ISSET (flowcontrol_pipe[0], &fds))
- {
- while (read (flowcontrol_pipe[0], buf, 1) == 1)
- {
- if (*buf == 'S') /* Stop */
- paused = 1;
- else if (*buf == 'G') /* Go */
- paused = 0;
- else
- return; /* ??? */
- }
- }
- }
- }
- #endif /* SERVER_FLOWCONTROL */
-
- static void output_dir PROTO((char *, char *));
-
- static void
- output_dir (update_dir, repository)
- char *update_dir;
- char *repository;
- {
- if (use_dir_and_repos)
- {
- if (update_dir[0] == '\0')
- buf_output0 (&protocol, ".");
- else
- buf_output0 (&protocol, update_dir);
- buf_output0 (&protocol, "/\n");
- }
- buf_output0 (&protocol, repository);
- buf_output0 (&protocol, "/");
- }
-
- /*
- * Entries line that we are squirreling away to send to the client when
- * we are ready.
- */
- static char *entries_line;
-
- /*
- * File which has been Scratch_File'd, we are squirreling away that fact
- * to inform the client when we are ready.
- */
- static char *scratched_file;
-
- /*
- * The scratched_file will need to be removed as well as having its entry
- * removed.
- */
- static int kill_scratched_file;
-
- void
- server_register (name, version, timestamp, options, tag, date, conflict)
- char *name;
- char *version;
- char *timestamp;
- char *options;
- char *tag;
- char *date;
- char *conflict;
- {
- int len;
-
- if (options == NULL)
- options = "";
-
- if (trace)
- {
- (void) fprintf (stderr,
- "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n",
- (server_active) ? 'S' : ' ', /* silly */
- name, version, timestamp, options, tag ? tag : "",
- date ? date : "", conflict ? conflict : "");
- }
-
- if (entries_line != NULL)
- {
- /*
- * If CVS decides to Register it more than once (which happens
- * on "cvs update foo/foo.c" where foo and foo.c are already
- * checked out), use the last of the entries lines Register'd.
- */
- free (entries_line);
- }
-
- /*
- * I have reports of Scratch_Entry and Register both happening, in
- * two different cases. Using the last one which happens is almost
- * surely correct; I haven't tracked down why they both happen (or
- * even verified that they are for the same file).
- */
- if (scratched_file != NULL)
- {
- free (scratched_file);
- scratched_file = NULL;
- }
-
- len = (strlen (name) + strlen (version) + strlen (options) + 80);
- if (tag)
- len += strlen (tag);
- if (date)
- len += strlen (date);
-
- entries_line = xmalloc (len);
- sprintf (entries_line, "/%s/%s/", name, version);
- if (conflict != NULL)
- {
- strcat (entries_line, "+=");
- }
- strcat (entries_line, "/");
- strcat (entries_line, options);
- strcat (entries_line, "/");
- if (tag != NULL)
- {
- strcat (entries_line, "T");
- strcat (entries_line, tag);
- }
- else if (date != NULL)
- {
- strcat (entries_line, "D");
- strcat (entries_line, date);
- }
- }
-
- void
- server_scratch (fname)
- char *fname;
- {
- /*
- * I have reports of Scratch_Entry and Register both happening, in
- * two different cases. Using the last one which happens is almost
- * surely correct; I haven't tracked down why they both happen (or
- * even verified that they are for the same file).
- */
- if (entries_line != NULL)
- {
- free (entries_line);
- entries_line = NULL;
- }
-
- if (scratched_file != NULL)
- {
- buf_output0 (&protocol,
- "E CVS server internal error: duplicate Scratch_Entry\n");
- buf_send_counted (&protocol);
- return;
- }
- scratched_file = xstrdup (fname);
- kill_scratched_file = 1;
- }
-
- void
- server_scratch_entry_only ()
- {
- kill_scratched_file = 0;
- }
-
- /* Print a new entries line, from a previous server_register. */
- static void
- new_entries_line ()
- {
- if (entries_line)
- {
- buf_output0 (&protocol, entries_line);
- buf_output (&protocol, "\n", 1);
- }
- else
- /* Return the error message as the Entries line. */
- buf_output0 (&protocol,
- "CVS server internal error: Register missing\n");
- free (entries_line);
- entries_line = NULL;
- }
-
- static void
- serve_ci (arg)
- char *arg;
- {
- do_cvs_command (commit);
- }
-
- static void
- checked_in_response (file, update_dir, repository)
- char *file;
- char *update_dir;
- char *repository;
- {
- if (supported_response ("Mode"))
- {
- struct stat sb;
- char *mode_string;
-
- if (stat (file, &sb) < 0)
- {
- /* Not clear to me why the file would fail to exist, but it
- was happening somewhere in the testsuite. */
- if (!existence_error (errno))
- error (0, errno, "cannot stat %s", file);
- }
- else
- {
- buf_output0 (&protocol, "Mode ");
- mode_string = mode_to_string (sb.st_mode);
- buf_output0 (&protocol, mode_string);
- buf_output0 (&protocol, "\n");
- free (mode_string);
- }
- }
-
- buf_output0 (&protocol, "Checked-in ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
- new_entries_line ();
- }
-
- void
- server_checked_in (file, update_dir, repository)
- char *file;
- char *update_dir;
- char *repository;
- {
- if (noexec)
- return;
- if (scratched_file != NULL && entries_line == NULL)
- {
- /*
- * This happens if we are now doing a "cvs remove" after a previous
- * "cvs add" (without a "cvs ci" in between).
- */
- buf_output0 (&protocol, "Remove-entry ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
- free (scratched_file);
- scratched_file = NULL;
- }
- else
- {
- checked_in_response (file, update_dir, repository);
- }
- buf_send_counted (&protocol);
- }
-
- void
- server_update_entries (file, update_dir, repository, updated)
- char *file;
- char *update_dir;
- char *repository;
- enum server_updated_arg4 updated;
- {
- if (noexec)
- return;
- if (updated == SERVER_UPDATED)
- checked_in_response (file, update_dir, repository);
- else
- {
- if (!supported_response ("New-entry"))
- return;
- buf_output0 (&protocol, "New-entry ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
- new_entries_line ();
- }
-
- buf_send_counted (&protocol);
- }
-
- static void
- serve_update (arg)
- char *arg;
- {
- do_cvs_command (update);
- }
-
- static void
- serve_diff (arg)
- char *arg;
- {
- do_cvs_command (diff);
- }
-
- static void
- serve_log (arg)
- char *arg;
- {
- do_cvs_command (cvslog);
- }
-
- static void
- serve_add (arg)
- char *arg;
- {
- do_cvs_command (add);
- }
-
- static void
- serve_remove (arg)
- char *arg;
- {
- do_cvs_command (cvsremove);
- }
-
- static void
- serve_status (arg)
- char *arg;
- {
- do_cvs_command (status);
- }
-
- static void
- serve_rdiff (arg)
- char *arg;
- {
- do_cvs_command (patch);
- }
-
- static void
- serve_tag (arg)
- char *arg;
- {
- do_cvs_command (tag);
- }
-
- static void
- serve_rtag (arg)
- char *arg;
- {
- do_cvs_command (rtag);
- }
-
- static void
- serve_import (arg)
- char *arg;
- {
- do_cvs_command (import);
- }
-
- static void
- serve_admin (arg)
- char *arg;
- {
- do_cvs_command (admin);
- }
-
- static void
- serve_history (arg)
- char *arg;
- {
- do_cvs_command (history);
- }
-
- static void
- serve_release (arg)
- char *arg;
- {
- do_cvs_command (release);
- }
-
- static void serve_watch_on PROTO ((char *));
-
- static void
- serve_watch_on (arg)
- char *arg;
- {
- do_cvs_command (watch_on);
- }
-
- static void serve_watch_off PROTO ((char *));
-
- static void
- serve_watch_off (arg)
- char *arg;
- {
- do_cvs_command (watch_off);
- }
-
- static void serve_watch_add PROTO ((char *));
-
- static void
- serve_watch_add (arg)
- char *arg;
- {
- do_cvs_command (watch_add);
- }
-
- static void serve_watch_remove PROTO ((char *));
-
- static void
- serve_watch_remove (arg)
- char *arg;
- {
- do_cvs_command (watch_remove);
- }
-
- static void serve_watchers PROTO ((char *));
-
- static void
- serve_watchers (arg)
- char *arg;
- {
- do_cvs_command (watchers);
- }
-
- static void serve_editors PROTO ((char *));
-
- static void
- serve_editors (arg)
- char *arg;
- {
- do_cvs_command (editors);
- }
-
- static int noop PROTO ((int, char **));
-
- static int
- noop (argc, argv)
- int argc;
- char **argv;
- {
- return 0;
- }
-
- static void serve_noop PROTO ((char *));
-
- static void
- serve_noop (arg)
- char *arg;
- {
- do_cvs_command (noop);
- }
-
- static void serve_init PROTO ((char *));
-
- static void
- serve_init (arg)
- char *arg;
- {
- CVSroot = malloc (strlen (arg) + 1);
- if (CVSroot == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (CVSroot, arg);
-
- do_cvs_command (init);
- }
-
- static void serve_annotate PROTO ((char *));
-
- static void
- serve_annotate (arg)
- char *arg;
- {
- do_cvs_command (annotate);
- }
-
- static void
- serve_co (arg)
- char *arg;
- {
- char *tempdir;
- int status;
-
- if (print_pending_error ())
- return;
-
- if (!isdir (CVSADM))
- {
- /*
- * The client has not sent a "Repository" line. Check out
- * into a pristine directory.
- */
- tempdir = malloc (strlen (server_temp_dir) + 80);
- if (tempdir == NULL)
- {
- printf ("E Out of memory\n");
- return;
- }
- strcpy (tempdir, server_temp_dir);
- strcat (tempdir, "/checkout-dir");
- status = mkdir_p (tempdir);
- if (status != 0 && status != EEXIST)
- {
- printf ("E Cannot create %s\n", tempdir);
- print_error (errno);
- free (tempdir);
- return;
- }
-
- if (chdir (tempdir) < 0)
- {
- printf ("E Cannot change to directory %s\n", tempdir);
- print_error (errno);
- free (tempdir);
- return;
- }
- free (tempdir);
- }
- do_cvs_command (checkout);
- }
-
- static void
- serve_export (arg)
- char *arg;
- {
- /* Tell checkout() to behave like export not checkout. */
- command_name = "export";
- serve_co (arg);
- }
-
- void
- server_copy_file (file, update_dir, repository, newfile)
- char *file;
- char *update_dir;
- char *repository;
- char *newfile;
- {
- if (!supported_response ("Copy-file"))
- return;
- buf_output0 (&protocol, "Copy-file ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output0 (&protocol, "\n");
- buf_output0 (&protocol, newfile);
- buf_output0 (&protocol, "\n");
- }
-
- void
- server_updated (file, update_dir, repository, updated, file_info, checksum)
- char *file;
- char *update_dir;
- char *repository;
- enum server_updated_arg4 updated;
- struct stat *file_info;
- unsigned char *checksum;
- {
- char *short_pathname;
-
- if (noexec)
- return;
-
- short_pathname = xmalloc (strlen (update_dir) + strlen (file) + 10);
- if (update_dir[0] == '\0')
- strcpy (short_pathname, file);
- else
- sprintf (short_pathname, "%s/%s", update_dir, file);
-
- if (entries_line != NULL && scratched_file == NULL)
- {
- FILE *f;
- struct stat sb;
- struct buffer_data *list, *last;
- unsigned long size;
- char size_text[80];
-
- if (stat (file, &sb) < 0)
- {
- if (existence_error (errno))
- {
- /*
- * If we have a sticky tag for a branch on which the
- * file is dead, and cvs update the directory, it gets
- * a T_CHECKOUT but no file. So in this case just
- * forget the whole thing.
- */
- free (entries_line);
- entries_line = NULL;
- goto done;
- }
- error (1, errno, "reading %s", short_pathname);
- }
-
- if (checksum != NULL)
- {
- static int checksum_supported = -1;
-
- if (checksum_supported == -1)
- {
- checksum_supported = supported_response ("Checksum");
- }
-
- if (checksum_supported)
- {
- int i;
- char buf[3];
-
- buf_output0 (&protocol, "Checksum ");
- for (i = 0; i < 16; i++)
- {
- sprintf (buf, "%02x", (unsigned int) checksum[i]);
- buf_output0 (&protocol, buf);
- }
- buf_append_char (&protocol, '\n');
- }
- }
-
- if (updated == SERVER_UPDATED)
- buf_output0 (&protocol, "Updated ");
- else if (updated == SERVER_MERGED)
- buf_output0 (&protocol, "Merged ");
- else if (updated == SERVER_PATCHED)
- buf_output0 (&protocol, "Patched ");
- else
- abort ();
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
-
- new_entries_line ();
-
- {
- char *mode_string;
-
- /* FIXME: When we check out files the umask of the server
- (set in .bashrc if rsh is in use, or set in main.c in
- the kerberos case, I think) affects what mode we send,
- and it shouldn't. */
- if (file_info != NULL)
- mode_string = mode_to_string (file_info->st_mode);
- else
- mode_string = mode_to_string (sb.st_mode);
- buf_output0 (&protocol, mode_string);
- buf_output0 (&protocol, "\n");
- free (mode_string);
- }
-
- list = last = NULL;
- size = 0;
- if (sb.st_size > 0)
- {
- /* Throughout this section we use binary mode to read the
- file we are sending. The client handles any line ending
- translation if necessary. */
-
- if (gzip_level
- /*
- * For really tiny files, the gzip process startup
- * time will outweigh the compression savings. This
- * might be computable somehow; using 100 here is just
- * a first approximation.
- */
- && sb.st_size > 100)
- {
- int status, fd, gzip_status;
- pid_t gzip_pid;
-
- fd = open (file, O_RDONLY | OPEN_BINARY, 0);
- if (fd < 0)
- error (1, errno, "reading %s", short_pathname);
- fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid);
- f = fdopen (fd, "rb");
- status = buf_read_file_to_eof (f, &list, &last);
- size = buf_chain_length (list);
- if (status == -2)
- (*protocol.memory_error) (&protocol);
- else if (status != 0)
- error (1, ferror (f) ? errno : 0, "reading %s",
- short_pathname);
- if (fclose (f) == EOF)
- error (1, errno, "reading %s", short_pathname);
- if (waitpid (gzip_pid, &gzip_status, 0) == -1)
- error (1, errno, "waiting for gzip process %ld",
- (long) gzip_pid);
- else if (gzip_status != 0)
- error (1, 0, "gzip exited %d", gzip_status);
- /* Prepending length with "z" is flag for using gzip here. */
- buf_output0 (&protocol, "z");
- }
- else
- {
- long status;
-
- size = sb.st_size;
- f = fopen (file, "rb");
- if (f == NULL)
- error (1, errno, "reading %s", short_pathname);
- status = buf_read_file (f, sb.st_size, &list, &last);
- if (status == -2)
- (*protocol.memory_error) (&protocol);
- else if (status != 0)
- error (1, ferror (f) ? errno : 0, "reading %s",
- short_pathname);
- if (fclose (f) == EOF)
- error (1, errno, "reading %s", short_pathname);
- }
- }
-
- sprintf (size_text, "%lu\n", size);
- buf_output0 (&protocol, size_text);
-
- buf_append_data (&protocol, list, last);
- /* Note we only send a newline here if the file ended with one. */
-
- /*
- * Avoid using up too much disk space for temporary files.
- * A file which does not exist indicates that the file is up-to-date,
- * which is now the case. If this is SERVER_MERGED, the file is
- * not up-to-date, and we indicate that by leaving the file there.
- * I'm thinking of cases like "cvs update foo/foo.c foo".
- */
- if ((updated == SERVER_UPDATED || updated == SERVER_PATCHED)
- /* But if we are joining, we'll need the file when we call
- join_file. */
- && !joining ())
- unlink (file);
- }
- else if (scratched_file != NULL && entries_line == NULL)
- {
- if (strcmp (scratched_file, file) != 0)
- error (1, 0,
- "CVS server internal error: `%s' vs. `%s' scratched",
- scratched_file,
- file);
- free (scratched_file);
- scratched_file = NULL;
-
- if (kill_scratched_file)
- buf_output0 (&protocol, "Removed ");
- else
- buf_output0 (&protocol, "Remove-entry ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
- }
- else if (scratched_file == NULL && entries_line == NULL)
- {
- /*
- * This can happen with death support if we were processing
- * a dead file in a checkout.
- */
- }
- else
- error (1, 0,
- "CVS server internal error: Register *and* Scratch_Entry.\n");
- buf_send_counted (&protocol);
- done:
- free (short_pathname);
- }
-
- void
- server_set_entstat (update_dir, repository)
- char *update_dir;
- char *repository;
- {
- static int set_static_supported = -1;
- if (set_static_supported == -1)
- set_static_supported = supported_response ("Set-static-directory");
- if (!set_static_supported) return;
-
- buf_output0 (&protocol, "Set-static-directory ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
- buf_send_counted (&protocol);
- }
-
- void
- server_clear_entstat (update_dir, repository)
- char *update_dir;
- char *repository;
- {
- static int clear_static_supported = -1;
- if (clear_static_supported == -1)
- clear_static_supported = supported_response ("Clear-static-directory");
- if (!clear_static_supported) return;
-
- if (noexec)
- return;
-
- buf_output0 (&protocol, "Clear-static-directory ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
- buf_send_counted (&protocol);
- }
-
- void
- server_set_sticky (update_dir, repository, tag, date)
- char *update_dir;
- char *repository;
- char *tag;
- char *date;
- {
- static int set_sticky_supported = -1;
- if (set_sticky_supported == -1)
- set_sticky_supported = supported_response ("Set-sticky");
- if (!set_sticky_supported) return;
-
- if (noexec)
- return;
-
- if (tag == NULL && date == NULL)
- {
- buf_output0 (&protocol, "Clear-sticky ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
- }
- else
- {
- buf_output0 (&protocol, "Set-sticky ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
- if (tag != NULL)
- {
- buf_output0 (&protocol, "T");
- buf_output0 (&protocol, tag);
- }
- else
- {
- buf_output0 (&protocol, "D");
- buf_output0 (&protocol, date);
- }
- buf_output0 (&protocol, "\n");
- }
- buf_send_counted (&protocol);
- }
-
- struct template_proc_data
- {
- char *update_dir;
- char *repository;
- };
-
- /* Here as a static until we get around to fixing Parse_Info to pass along
- a void * for it. */
- static struct template_proc_data *tpd;
-
- static int
- template_proc (repository, template)
- char *repository;
- char *template;
- {
- FILE *fp;
- char buf[1024];
- size_t n;
- struct stat sb;
- struct template_proc_data *data = tpd;
-
- if (!supported_response ("Template"))
- /* Might want to warn the user that the rcsinfo feature won't work. */
- return 0;
- buf_output0 (&protocol, "Template ");
- output_dir (data->update_dir, data->repository);
- buf_output0 (&protocol, "\n");
-
- fp = fopen (template, "rb");
- if (fp == NULL)
- {
- error (0, errno, "Couldn't open rcsinfo template file %s", template);
- return 1;
- }
- if (fstat (fileno (fp), &sb) < 0)
- {
- error (0, errno, "cannot stat rcsinfo template file %s", template);
- return 1;
- }
- sprintf (buf, "%ld\n", (long) sb.st_size);
- buf_output0 (&protocol, buf);
- while (!feof (fp))
- {
- n = fread (buf, 1, sizeof buf, fp);
- buf_output (&protocol, buf, n);
- if (ferror (fp))
- {
- error (0, errno, "cannot read rcsinfo template file %s", template);
- (void) fclose (fp);
- return 1;
- }
- }
- if (fclose (fp) < 0)
- error (0, errno, "cannot close rcsinfo template file %s", template);
- return 0;
- }
-
- void
- server_template (update_dir, repository)
- char *update_dir;
- char *repository;
- {
- struct template_proc_data data;
- data.update_dir = update_dir;
- data.repository = repository;
- tpd = &data;
- (void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc, 1);
- }
-
- static void
- serve_gzip_contents (arg)
- char *arg;
- {
- int level;
- level = atoi (arg);
- if (level == 0)
- level = 6;
- gzip_level = level;
- }
-
- static void
- serve_ignore (arg)
- char *arg;
- {
- /*
- * Just ignore this command. This is used to support the
- * update-patches command, which is not a real command, but a signal
- * to the client that update will accept the -u argument.
- */
- }
-
- static int
- expand_proc (pargc, argv, where, mwhere, mfile, shorten,
- local_specified, omodule, msg)
- int *pargc;
- char **argv;
- char *where;
- char *mwhere;
- char *mfile;
- int shorten;
- int local_specified;
- char *omodule;
- char *msg;
- {
- int i;
- char *dir = argv[0];
-
- /* If mwhere has been specified, the thing we're expanding is a
- module -- just return its name so the client will ask for the
- right thing later. If it is an alias or a real directory,
- mwhere will not be set, so send out the appropriate
- expansion. */
-
- if (mwhere != NULL)
- {
- printf ("Module-expansion %s", mwhere);
- if (mfile != NULL)
- {
- printf ("/%s", mfile);
- }
- printf ("\n");
- }
- else
- {
- /* We may not need to do this anymore -- check the definition
- of aliases before removing */
- if (*pargc == 1)
- printf ("Module-expansion %s\n", dir);
- else
- for (i = 1; i < *pargc; ++i)
- printf ("Module-expansion %s/%s\n", dir, argv[i]);
- }
- return 0;
- }
-
- static void
- serve_expand_modules (arg)
- char *arg;
- {
- int i;
- int err;
- DBM *db;
- err = 0;
-
- /*
- * FIXME: error handling is bogus; do_module can write to stdout and/or
- * stderr and we're not using do_cvs_command.
- */
-
- server_expanding = 1;
- db = open_module ();
- for (i = 1; i < argument_count; i++)
- err += do_module (db, argument_vector[i],
- CHECKOUT, "Updating", expand_proc,
- NULL, 0, 0, 0,
- (char *) NULL);
- close_module (db);
- server_expanding = 0;
- {
- /* argument_vector[0] is a dummy argument, we don't mess with it. */
- char **cp;
- for (cp = argument_vector + 1;
- cp < argument_vector + argument_count;
- ++cp)
- free (*cp);
-
- argument_count = 1;
- }
- if (err)
- /* We will have printed an error message already. */
- printf ("error \n");
- else
- printf ("ok\n");
- }
-
- void
- server_prog (dir, name, which)
- char *dir;
- char *name;
- enum progs which;
- {
- if (!supported_response ("Set-checkin-prog"))
- {
- printf ("E \
- warning: this client does not support -i or -u flags in the modules file.\n");
- return;
- }
- switch (which)
- {
- case PROG_CHECKIN:
- printf ("Set-checkin-prog ");
- break;
- case PROG_UPDATE:
- printf ("Set-update-prog ");
- break;
- }
- printf ("%s\n%s\n", dir, name);
- }
-
- static void
- serve_checkin_prog (arg)
- char *arg;
- {
- FILE *f;
- f = fopen (CVSADM_CIPROG, "w+");
- if (f == NULL)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_CIPROG);
- return;
- }
- if (fprintf (f, "%s\n", arg) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_CIPROG);
- return;
- }
- if (fclose (f) == EOF)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_CIPROG);
- return;
- }
- }
-
- static void
- serve_update_prog (arg)
- char *arg;
- {
- FILE *f;
- f = fopen (CVSADM_UPROG, "w+");
- if (f == NULL)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_UPROG);
- return;
- }
- if (fprintf (f, "%s\n", arg) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_UPROG);
- return;
- }
- if (fclose (f) == EOF)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_UPROG);
- return;
- }
- }
-
- static void serve_valid_requests PROTO((char *arg));
-
- #endif /* SERVER_SUPPORT */
- #if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
-
- /*
- * Parts of this table are shared with the client code,
- * but the client doesn't need to know about the handler
- * functions.
- */
-
- struct request requests[] =
- {
- #ifdef SERVER_SUPPORT
- #define REQ_LINE(n, f, s) {n, f, s}
- #else
- #define REQ_LINE(n, f, s) {n, s}
- #endif
-
- REQ_LINE("Root", serve_root, rq_essential),
- REQ_LINE("Valid-responses", serve_valid_responses, rq_essential),
- REQ_LINE("valid-requests", serve_valid_requests, rq_essential),
- REQ_LINE("Repository", serve_repository, rq_essential),
- REQ_LINE("Directory", serve_directory, rq_optional),
- REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional),
- REQ_LINE("Static-directory", serve_static_directory, rq_optional),
- REQ_LINE("Sticky", serve_sticky, rq_optional),
- REQ_LINE("Checkin-prog", serve_checkin_prog, rq_optional),
- REQ_LINE("Update-prog", serve_update_prog, rq_optional),
- REQ_LINE("Entry", serve_entry, rq_essential),
- REQ_LINE("Modified", serve_modified, rq_essential),
- REQ_LINE("Lost", serve_lost, rq_optional),
- REQ_LINE("UseUnchanged", serve_enable_unchanged, rq_enableme),
- REQ_LINE("Unchanged", serve_unchanged, rq_optional),
- REQ_LINE("Notify", serve_notify, rq_optional),
- REQ_LINE("Questionable", serve_questionable, rq_optional),
- REQ_LINE("Case", serve_case, rq_optional),
- REQ_LINE("Argument", serve_argument, rq_essential),
- REQ_LINE("Argumentx", serve_argumentx, rq_essential),
- REQ_LINE("Global_option", serve_global_option, rq_optional),
- REQ_LINE("Set", serve_set, rq_optional),
- REQ_LINE("expand-modules", serve_expand_modules, rq_optional),
- REQ_LINE("ci", serve_ci, rq_essential),
- REQ_LINE("co", serve_co, rq_essential),
- REQ_LINE("update", serve_update, rq_essential),
- REQ_LINE("diff", serve_diff, rq_optional),
- REQ_LINE("log", serve_log, rq_optional),
- REQ_LINE("add", serve_add, rq_optional),
- REQ_LINE("remove", serve_remove, rq_optional),
- REQ_LINE("update-patches", serve_ignore, rq_optional),
- REQ_LINE("gzip-file-contents", serve_gzip_contents, rq_optional),
- REQ_LINE("status", serve_status, rq_optional),
- REQ_LINE("rdiff", serve_rdiff, rq_optional),
- REQ_LINE("tag", serve_tag, rq_optional),
- REQ_LINE("rtag", serve_rtag, rq_optional),
- REQ_LINE("import", serve_import, rq_optional),
- REQ_LINE("admin", serve_admin, rq_optional),
- REQ_LINE("export", serve_export, rq_optional),
- REQ_LINE("history", serve_history, rq_optional),
- REQ_LINE("release", serve_release, rq_optional),
- REQ_LINE("watch-on", serve_watch_on, rq_optional),
- REQ_LINE("watch-off", serve_watch_off, rq_optional),
- REQ_LINE("watch-add", serve_watch_add, rq_optional),
- REQ_LINE("watch-remove", serve_watch_remove, rq_optional),
- REQ_LINE("watchers", serve_watchers, rq_optional),
- REQ_LINE("editors", serve_editors, rq_optional),
- REQ_LINE("init", serve_init, rq_optional),
- REQ_LINE("annotate", serve_annotate, rq_optional),
- REQ_LINE("noop", serve_noop, rq_optional),
- REQ_LINE(NULL, NULL, rq_optional)
-
- #undef REQ_LINE
- };
-
- #endif /* SERVER_SUPPORT or CLIENT_SUPPORT */
- #ifdef SERVER_SUPPORT
-
- static void
- serve_valid_requests (arg)
- char *arg;
- {
- struct request *rq;
- if (print_pending_error ())
- return;
- printf ("Valid-requests");
- for (rq = requests; rq->name != NULL; rq++)
- if (rq->func != NULL)
- printf (" %s", rq->name);
- printf ("\nok\n");
- }
-
- #ifdef sun
- /*
- * Delete temporary files. SIG is the signal making this happen, or
- * 0 if not called as a result of a signal.
- */
- static int command_pid_is_dead;
- static void wait_sig (sig)
- int sig;
- {
- int status;
- pid_t r = wait (&status);
- if (r == command_pid)
- command_pid_is_dead++;
- }
- #endif
-
- void
- server_cleanup (sig)
- int sig;
- {
- /* Do "rm -rf" on the temp directory. */
- int len;
- char *cmd;
- char *temp_dir;
-
- if (dont_delete_temp)
- return;
-
- /* What a bogus kludge. This disgusting code makes all kinds of
- assumptions about SunOS, and is only for a bug in that system.
- So only enable it on Suns. */
- #ifdef sun
- if (command_pid > 0) {
- /* To avoid crashes on SunOS due to bugs in SunOS tmpfs
- triggered by the use of rename() in RCS, wait for the
- subprocess to die. Unfortunately, this means draining output
- while waiting for it to unblock the signal we sent it. Yuck! */
- int status;
- pid_t r;
-
- signal (SIGCHLD, wait_sig);
- if (sig)
- /* Perhaps SIGTERM would be more correct. But the child
- process will delay the SIGINT delivery until its own
- children have exited. */
- kill (command_pid, SIGINT);
- /* The caller may also have sent a signal to command_pid, so
- always try waiting. First, though, check and see if it's still
- there.... */
- do_waitpid:
- r = waitpid (command_pid, &status, WNOHANG);
- if (r == 0)
- ;
- else if (r == command_pid)
- command_pid_is_dead++;
- else if (r == -1)
- switch (errno) {
- case ECHILD:
- command_pid_is_dead++;
- break;
- case EINTR:
- goto do_waitpid;
- }
- else
- /* waitpid should always return one of the above values */
- abort ();
- while (!command_pid_is_dead) {
- struct timeval timeout;
- struct fd_set_wrapper readfds;
- char buf[100];
- int i;
-
- /* Use a non-zero timeout to avoid eating up CPU cycles. */
- timeout.tv_sec = 2;
- timeout.tv_usec = 0;
- readfds = command_fds_to_drain;
- switch (select (max_command_fd + 1, &readfds.fds,
- (fd_set *)0, (fd_set *)0,
- &timeout)) {
- case -1:
- if (errno != EINTR)
- abort ();
- case 0:
- /* timeout */
- break;
- case 1:
- for (i = 0; i <= max_command_fd; i++)
- {
- if (!FD_ISSET (i, &readfds.fds))
- continue;
- /* this fd is non-blocking */
- while (read (i, buf, sizeof (buf)) >= 1)
- ;
- }
- break;
- default:
- abort ();
- }
- }
- }
- #endif
-
- /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
- temp_dir = getenv ("TMPDIR");
- if (temp_dir == NULL || temp_dir[0] == '\0')
- temp_dir = "/tmp";
- chdir(temp_dir);
-
- len = strlen (server_temp_dir) + 80;
- cmd = malloc (len);
- if (cmd == NULL)
- {
- printf ("E Cannot delete %s on server; out of memory\n",
- server_temp_dir);
- return;
- }
- sprintf (cmd, "rm -rf %s", server_temp_dir);
- system (cmd);
- free (cmd);
- }
-
- int server_active = 0;
- int server_expanding = 0;
-
- int
- server (argc, argv)
- int argc;
- char **argv;
- {
- if (argc == -1)
- {
- static const char *const msg[] =
- {
- "Usage: %s %s\n",
- " Normally invoked by a cvs client on a remote machine.\n",
- NULL
- };
- usage (msg);
- }
- /* Ignore argc and argv. They might be from .cvsrc. */
-
- /* Since we're in the server parent process, error should use the
- protocol to report error messages. */
- error_use_protocol = 1;
-
- /*
- * Put Rcsbin at the start of PATH, so that rcs programs can find
- * themselves.
- */
- #ifdef HAVE_PUTENV
- if (Rcsbin != NULL && *Rcsbin)
- {
- char *p;
- char *env;
-
- p = getenv ("PATH");
- if (p != NULL)
- {
- env = malloc (strlen (Rcsbin) + strlen (p) + sizeof "PATH=:");
- if (env != NULL)
- sprintf (env, "PATH=%s:%s", Rcsbin, p);
- }
- else
- {
- env = malloc (strlen (Rcsbin) + sizeof "PATH=");
- if (env != NULL)
- sprintf (env, "PATH=%s", Rcsbin);
- }
- if (env == NULL)
- {
- printf ("E Fatal server error, aborting.\n\
- error ENOMEM Virtual memory exhausted.\n");
- exit (EXIT_FAILURE);
- }
- putenv (env);
- }
- #endif
-
- /* OK, now figure out where we stash our temporary files. */
- {
- char *p;
-
- /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
- char *temp_dir = getenv ("TMPDIR");
- if (temp_dir == NULL || temp_dir[0] == '\0')
- temp_dir = "/tmp";
-
- server_temp_dir = malloc (strlen (temp_dir) + 80);
- if (server_temp_dir == NULL)
- {
- /*
- * Strictly speaking, we're not supposed to output anything
- * now. But we're about to exit(), give it a try.
- */
- printf ("E Fatal server error, aborting.\n\
- error ENOMEM Virtual memory exhausted.\n");
- exit (EXIT_FAILURE);
- }
- strcpy (server_temp_dir, temp_dir);
-
- /* Remove a trailing slash from TMPDIR if present. */
- p = server_temp_dir + strlen (server_temp_dir) - 1;
- if (*p == '/')
- *p = '\0';
-
- /*
- * I wanted to use cvs-serv/PID, but then you have to worry about
- * the permissions on the cvs-serv directory being right. So
- * use cvs-servPID.
- */
- strcat (server_temp_dir, "/cvs-serv");
-
- p = server_temp_dir + strlen (server_temp_dir);
- sprintf (p, "%ld", (long) getpid ());
- }
-
- (void) SIG_register (SIGHUP, server_cleanup);
- (void) SIG_register (SIGINT, server_cleanup);
- (void) SIG_register (SIGQUIT, server_cleanup);
- (void) SIG_register (SIGPIPE, server_cleanup);
- (void) SIG_register (SIGTERM, server_cleanup);
-
- /* Now initialize our argument vector (for arguments from the client). */
-
- /* Small for testing. */
- argument_vector_size = 1;
- argument_vector =
- (char **) malloc (argument_vector_size * sizeof (char *));
- if (argument_vector == NULL)
- {
- /*
- * Strictly speaking, we're not supposed to output anything
- * now. But we're about to exit(), give it a try.
- */
- printf ("E Fatal server error, aborting.\n\
- error ENOMEM Virtual memory exhausted.\n");
- exit (EXIT_FAILURE);
- }
-
- argument_count = 1;
- argument_vector[0] = "Dummy argument 0";
-
- buf_to_net.data = buf_to_net.last = NULL;
- buf_to_net.fd = STDOUT_FILENO;
- buf_to_net.output = 1;
- buf_to_net.nonblocking = 0;
- buf_to_net.memory_error = outbuf_memory_error;
-
- server_active = 1;
-
- while (1)
- {
- char *cmd, *orig_cmd;
- struct request *rq;
-
- orig_cmd = cmd = read_line (stdin);
- if (cmd == NULL)
- break;
- if (cmd == NO_MEM_ERROR)
- {
- printf ("E Fatal server error, aborting.\n\
- error ENOMEM Virtual memory exhausted.\n");
- break;
- }
- for (rq = requests; rq->name != NULL; ++rq)
- if (strncmp (cmd, rq->name, strlen (rq->name)) == 0)
- {
- int len = strlen (rq->name);
- if (cmd[len] == '\0')
- cmd += len;
- else if (cmd[len] == ' ')
- cmd += len + 1;
- else
- /*
- * The first len characters match, but it's a different
- * command. e.g. the command is "cooperate" but we matched
- * "co".
- */
- continue;
- (*rq->func) (cmd);
- break;
- }
- if (rq->name == NULL)
- {
- if (!print_pending_error ())
- printf ("error unrecognized request `%s'\n", cmd);
- }
- free (orig_cmd);
- }
- server_cleanup (0);
- return 0;
- }
-
-
- #ifdef AUTH_SERVER_SUPPORT
-
- extern char *crypt PROTO((const char *, const char *));
-
- /* This was test code, which we may need again. */
- #if 0
- /* If we were invoked this way, then stdin comes from the
- client and stdout/stderr writes to it. */
- int c;
- while ((c = getc (stdin)) != EOF && c != '*')
- {
- printf ("%c", toupper (c));
- fflush (stdout);
- }
- exit (0);
- #endif /* 1/0 */
-
-
- /*
- * 0 means no entry found for this user.
- * 1 means entry found and password matches.
- * 2 means entry found, but password does not match.
- */
- int
- check_repository_password (username, password, repository, host_user_ptr)
- char *username, *password, *repository, **host_user_ptr;
- {
- int retval = 0;
- FILE *fp;
- char *filename;
- char *linebuf;
- int found_it = 0;
- int namelen;
-
- filename = xmalloc (strlen (repository)
- + 1
- + strlen ("CVSROOT")
- + 1
- + strlen ("passwd")
- + 1);
-
- strcpy (filename, repository);
- strcat (filename, "/CVSROOT");
- strcat (filename, "/passwd");
-
- fp = fopen (filename, "r");
- if (fp == NULL)
- {
- if (!existence_error (errno))
- error (0, errno, "cannot open %s", filename);
- return 0;
- }
-
- /* Look for a relevant line -- one with this user's name. */
- namelen = strlen (username);
- while (1)
- {
- linebuf = read_line(fp);
- if (linebuf == NULL)
- {
- free (linebuf);
- break;
- }
- if (linebuf == NO_MEM_ERROR)
- {
- error (0, errno, "out of memory");
- break;
- }
- if ((strncmp (linebuf, username, namelen) == 0)
- && (linebuf[namelen] == ':'))
- {
- found_it = 1;
- break;
- }
- free (linebuf);
-
- }
- if (ferror (fp))
- error (0, errno, "cannot read %s", filename);
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", filename);
-
- /* If found_it != 0, then linebuf contains the information we need. */
- if (found_it)
- {
- char *found_password;
-
- strtok (linebuf, ":");
- found_password = strtok (NULL, ": \n");
- *host_user_ptr = strtok (NULL, ": \n");
- if (*host_user_ptr == NULL) *host_user_ptr = username;
- if (strcmp (found_password, crypt (password, found_password)) == 0)
- retval = 1;
- else
- retval = 2;
- }
- else
- {
- *host_user_ptr = NULL;
- retval = 0;
- }
-
- free (filename);
-
- return retval;
- }
-
-
- /* Return a hosting username if password matches, else NULL. */
- char *
- check_password (username, password, repository)
- char *username, *password, *repository;
- {
- int rc;
- char *host_user;
-
- /* First we see if this user has a password in the CVS-specific
- password file. If so, that's enough to authenticate with. If
- not, we'll check /etc/passwd. */
-
- rc = check_repository_password (username, password, repository,
- &host_user);
-
- if (rc == 1)
- return host_user;
- else if (rc == 2)
- return 0;
- else if (rc == 0)
- {
- /* No cvs password found, so try /etc/passwd. */
-
- struct passwd *pw;
- char *found_passwd;
-
- pw = getpwnam (username);
- if (pw == NULL)
- {
- printf ("E Fatal error, aborting.\n\
- error 0 %s: no such user\n", username);
- exit (EXIT_FAILURE);
- }
- found_passwd = pw->pw_passwd;
-
- if (found_passwd && *found_passwd)
- return ((! strcmp (found_passwd, crypt (password, found_passwd)))
- ? username : NULL);
- else if (password && *password)
- return username;
- else
- return NULL;
- }
- else
- {
- /* Something strange happened. We don't know what it was, but
- we certainly won't grant authorization. */
- return NULL;
- }
- }
-
-
- /* Read username and password from client (i.e., stdin).
- If correct, then switch to run as that user and send an ACK to the
- client via stdout, else send NACK and die. */
- void
- authenticate_connection ()
- {
- char tmp[PATH_MAX];
- char repository[PATH_MAX];
- char username[PATH_MAX];
- char password[PATH_MAX];
- char *host_user;
- char *descrambled_password;
- struct passwd *pw;
- int verify_and_exit = 0;
-
- /* The Authentication Protocol. Client sends:
- *
- * BEGIN AUTH REQUEST\n
- * <REPOSITORY>\n
- * <USERNAME>\n
- * <PASSWORD>\n
- * END AUTH REQUEST\n
- *
- * Server uses above information to authenticate, then sends
- *
- * I LOVE YOU\n
- *
- * if it grants access, else
- *
- * I HATE YOU\n
- *
- * if it denies access (and it exits if denying).
- *
- * When the client is "cvs login", the user does not desire actual
- * repository access, but would like to confirm the password with
- * the server. In this case, the start and stop strings are
- *
- * BEGIN VERIFICATION REQUEST\n
- *
- * and
- *
- * END VERIFICATION REQUEST\n
- *
- * On a verification request, the server's responses are the same
- * (with the obvious semantics), but it exits immediately after
- * sending the response in both cases.
- *
- * Why is the repository sent? Well, note that the actual
- * client/server protocol can't start up until authentication is
- * successful. But in order to perform authentication, the server
- * needs to look up the password in the special CVS passwd file,
- * before trying /etc/passwd. So the client transmits the
- * repository as part of the "authentication protocol". The
- * repository will be redundantly retransmitted later, but that's no
- * big deal.
- */
-
- /* Since we're in the server parent process, error should use the
- protocol to report error messages. */
- error_use_protocol = 1;
-
- /* Make sure the protocol starts off on the right foot... */
- fgets (tmp, PATH_MAX, stdin);
- if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
- verify_and_exit = 1;
- else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0)
- error (1, 0, "bad auth protocol start: %s", tmp);
-
- /* Get the three important pieces of information in order. */
- fgets (repository, PATH_MAX, stdin);
- fgets (username, PATH_MAX, stdin);
- fgets (password, PATH_MAX, stdin);
-
- /* Make them pure. */
- strip_trailing_newlines (repository);
- strip_trailing_newlines (username);
- strip_trailing_newlines (password);
-
- /* ... and make sure the protocol ends on the right foot. */
- fgets (tmp, PATH_MAX, stdin);
- if (strcmp (tmp,
- verify_and_exit ?
- "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n")
- != 0)
- {
- error (1, 0, "bad auth protocol end: %s", tmp);
- }
-
- /* We need the real cleartext before we hash it. */
- descrambled_password = descramble (password);
- host_user = check_password (username, descrambled_password, repository);
- if (host_user)
- {
- printf ("I LOVE YOU\n");
- fflush (stdout);
- memset (descrambled_password, 0, strlen (descrambled_password));
- free (descrambled_password);
- }
- else
- {
- printf ("I HATE YOU\n");
- fflush (stdout);
- memset (descrambled_password, 0, strlen (descrambled_password));
- free (descrambled_password);
- exit (EXIT_FAILURE);
- }
-
- /* Don't go any farther if we're just responding to "cvs login". */
- if (verify_and_exit)
- exit (0);
-
- /* Switch to run as this user. */
- pw = getpwnam (host_user);
- if (pw == NULL)
- {
- error (1, 0,
- "fatal error, aborting.\nerror 0 %s: no such user\n",
- username);
- }
-
- #if HAVE_INITGROUPS
- initgroups (pw->pw_name, pw->pw_gid);
- #endif /* HAVE_INITGROUPS */
-
- setgid (pw->pw_gid);
- setuid (pw->pw_uid);
- /* Inhibit access by randoms. Don't want people randomly
- changing our temporary tree before we check things in. */
- umask (077);
-
- #if HAVE_PUTENV
- /* Set LOGNAME and USER in the environment, in case they are
- already set to something else. */
- {
- char *env;
-
- env = xmalloc (sizeof "LOGNAME=" + strlen (username));
- (void) sprintf (env, "LOGNAME=%s", username);
- (void) putenv (env);
-
- env = xmalloc (sizeof "USER=" + strlen (username));
- (void) sprintf (env, "USER=%s", username);
- (void) putenv (env);
- }
- #endif /* HAVE_PUTENV */
- }
-
- #endif /* AUTH_SERVER_SUPPORT */
-
-
- #endif /* SERVER_SUPPORT */
-
- /* Output LEN bytes at STR. If LEN is zero, then output up to (not including)
- the first '\0' byte. Should not be called from the server parent process
- (yet at least, in the future it might be extended so that works). */
-
- void
- cvs_output (str, len)
- char *str;
- size_t len;
- {
- if (len == 0)
- len = strlen (str);
- if (error_use_protocol)
- /* Eventually we'll probably want to make it so this case works,
- but for now, callers who want to output something with
- error_use_protocol in effect can just printf the "M foo"
- themselves. */
- abort ();
- #ifdef SERVER_SUPPORT
- if (server_active)
- {
- buf_output (&saved_output, str, len);
- buf_copy_lines (&protocol, &saved_output, 'M');
- buf_send_counted (&protocol);
- }
- else
- #endif
- {
- size_t written;
- size_t to_write = len;
- char *p = str;
-
- while (to_write > 0)
- {
- written = fwrite (str, 1, to_write, stdout);
- if (written == 0)
- break;
- p += written;
- to_write -= written;
- }
- }
- }
-
- /* Like CVS_OUTPUT but output is for stderr not stdout. */
-
- void
- cvs_outerr (str, len)
- char *str;
- size_t len;
- {
- if (len == 0)
- len = strlen (str);
- if (error_use_protocol)
- /* Eventually we'll probably want to make it so this case works,
- but for now, callers who want to output something with
- error_use_protocol in effect can just printf the "E foo"
- themselves. */
- abort ();
- #ifdef SERVER_SUPPORT
- if (server_active)
- {
- buf_output (&saved_outerr, str, len);
- buf_copy_lines (&protocol, &saved_outerr, 'E');
- buf_send_counted (&protocol);
- }
- else
- #endif
- {
- size_t written;
- size_t to_write = len;
- char *p = str;
-
- while (to_write > 0)
- {
- written = fwrite (str, 1, to_write, stderr);
- if (written == 0)
- break;
- p += written;
- to_write -= written;
- }
- }
- }
-